قد تظهر تسلسل جديد في Java

ظهرت وثيقة بحث جديدة على موقع OpenJDK تصف فكرة إدخال تسلسل جديد محسّن في اللغة لاستبدال النسخة القديمة.

التسلسل في Java موجود منذ الإصدار 1.1 ، أي تقريبًا منذ اللحظة التي ولد فيها. من ناحية ، التسلسل هو آلية مريحة للغاية تسمح لك بسرعة وسهولة جعل أي فئة قابلة للتسلسل من خلال وراثة هذه الفئة من واجهة java.io.Serializable. ربما أصبحت هذه البساطة أحد الأسباب الرئيسية وراء اكتساب Java لهذه الشعبية الضخمة في العالم ، لأنها سمحت لك بكتابة تطبيقات الشبكة بسرعة وكفاءة.

من ناحية أخرى ، تشتمل الطريقة التي يتم بها تنفيذ التسلسل في Java على عدد كبير من المشكلات التي تزيد من تكلفة دعم التطبيقات ، وتقلل من أمانها ، وتبطئ تطور النظام الأساسي.

ما هو الخطأ في التسلسل في جافا؟ نحن ندرج في أخطر المشاكل:

  • التسلسل (وإلغاء التسلسل) يتجاوز آليات اللغة. يتجاهل معدّلات الوصول إلى المجال (خاصة ومحمية) ويقوم بإنشاء كائنات دون استخدام مُنشئات ، مما يعني أنه يتجاهل المتغيرات التي قد تكون موجودة في هذه المنشئات. يمكن للمهاجم استغلال مشكلة عدم الحصانة هذه عن طريق استبدال البيانات ببيانات غير صالحة ، وسيتم ابتلاعها بنجاح أثناء إلغاء التسلسل.
  • عند كتابة فئات قابلة للتسلسل ، لا يساعد المترجم بأي طريقة ولا يكتشف الأخطاء. على سبيل المثال ، لا يمكنك أن تضمن بشكل ثابت أن جميع حقول فئة قابلة للتسلسل هي نفسها قابلة للتسلسل. أو يمكنك إنشاء خطأ مطبعي في أسماء أساليب readObject ، و WriteObject ، و readResolve ، وما إلى ذلك ، ومن ثم لن يتم استخدام هذه الطرق أثناء التسلسل.
  • لا يدعم التسلسل آلية الإصدار العادي ، لذلك من الصعب للغاية تعديل الفئات القابلة للتسلسل بحيث تظل متوافقة مع الإصدارات القديمة.
  • يرتبط التسلسل بقوة ببث التشفير / فك التشفير ، مما يعني أنه من الصعب للغاية تغيير تنسيق التشفير إلى تنسيق مختلف عن التنسيق القياسي. بالإضافة إلى ذلك ، التنسيق القياسي ليس مضغوطًا أو فعالًا أو غير قابل للقراءة البشرية.

الخطأ الأساسي للتسلسل الموجود في Java هو أنه يحاول أن يكون "غير مرئي" للمبرمج. يرث ببساطة من java.io.Serializable ويتلقى بعض السحر الضمني الذي يتم تنفيذه بواسطة الجهاز الظاهري.
على العكس من ذلك ، يجب على المبرمج أن يكتب بشكل صريح الإنشاءات المسؤولة عن إنشاء الكائنات وتفكيكها. يجب أن تكون هذه التركيبات على مستوى اللغة ويجب كتابتها من خلال الوصول إلى الحقل الثابت ، وليس التفكير.

خطأ تسلسل آخر هو أنه يحاول القيام بالكثير. لقد حددت لنفسها مهمة التمكن من إجراء تسلسل لأي رسم تعسفي للكائنات (التي قد تحتوي على حلقات) وإلغاء تسلسلها مرة أخرى دون كسر حالتها.

يمكن تصحيح هذا الخطأ عن طريق تبسيط المهمة وتسلسلها وليس رسم بياني للكائنات ، ولكن شجرة البيانات التي لن يكون هناك مفهوم للهوية (كما هو الحال في JSON).

كيفية جعل التسلسل الذي يتناسب بشكل طبيعي مع طراز الكائن ، ويستخدم المنشئات لإزالة التسلسل ، يتم فصله عن تنسيق الترميز ويدعم تعيين الإصدار؟ تحقيقًا لهذه الغاية ، تأتي التعليقات التوضيحية قيد الإنقاذ وإمكانية وجود لغة لم يتم تضمينها بعد في Java: مطابقة الأنماط . على سبيل المثال:

public class Range { int lo; int hi; private Range(int lo, int hi) { if (lo > hi) throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); this.lo = lo; this.hi = hi; } @Serializer public pattern Range(int lo, int hi) { lo = this.lo; hi = this.hi; } @Deserializer public static Range make(int lo, int hi) { return new Range(lo, hi); } } 

في هذا المثال ، يتم الإعلان عن فئة النطاق ، وهي جاهزة للتسلسل من خلال عضوين خاصين من الفئة: مسلسل ومسلسل علامة عليه بتعليقات توضيحيةSerializer وDeserializer. يتم تطبيق التسلسل من خلال deconstructor من النموذج ، ويتم تنفيذ deserializer من خلال الأسلوب الثابت الذي يسمى المنشئ. وبالتالي ، أثناء إلغاء التسلسل ، يتم التحقق حتماً من الخيار hi> = lo المحدد في المُنشئ.
لا يوجد سحر في هذا النهج ، ويتم استخدام التعليقات التوضيحية العادية ، لذلك يمكن لأي إطار عمل التسلسل ، وليس فقط منصة Java نفسها. هذا يعني أن تنسيق الترميز يمكن أن يكون أيضًا أي شيء (ثنائي ، XML ، JSON ، YAML ، إلخ).

منذ المسلسلين و deserializers هي الطرق الشائعة ، والمبرمج لديه حرية كبيرة في تنفيذها. على سبيل المثال ، قد يختار تمثيلًا لكائن مختلفًا عن طريقة تمثيل الكائن في الذاكرة. على سبيل المثال ، يمكن ترتيب LinkedList ليس في سلسلة من الروابط ، ولكن في صفيف واحد مستمر ، مما سيجعل العرض التقديمي أكثر بساطة وأكثر كفاءة وأكثر إحكاما.

يتم تطبيق تعيين الإصدار في هذا النهج باستخدام حقل الإصدار الخاص في التعليقات التوضيحيةSerializer وDeserializer:

 class C { int a; int b; int c; @Deserializer(version = 3) public C(int a, int b, int c) { this a = a; this.b = b; this.c = c; } @Deserializer(version = 2) public C(int a, int b) { this(a, b, 0); } @Deserializer(version = 1) public C(int a) { this(a, 0, 0); } @Serializer(version = 3) public pattern C(int a, int b, int c) { a = this.a; b = this.b; c = this.c; } } 

في هذا المثال ، سيتم استدعاء أحد مزودي الخدمة الثلاثة ، اعتمادًا على الإصدار.
ماذا لو لم نكن نرغب في إتاحة المتسللين ومزيلي الشحن لأي شخص آخر غير لأغراض التسلسل؟ للقيام بذلك ، يمكننا أن نجعلها خاصة. ومع ذلك ، في هذه الحالة ، لن يتمكن إطار عمل تسلسل معين من الوصول إليها من خلال الانعكاس إذا كان هذا الرمز داخل الوحدة النمطية التي تكون الحزمة غير مفتوحة للوصول العاكس العميق. لمثل هذه الحالة ، يُقترح إدخال بنية جديدة أخرى في اللغة: أعضاء الفئة المفتوحة. على سبيل المثال:

 class Foo { private final InternalState is; public Foo(ExternalState es) { this(new InternalState(es)); } @Deserializer private open Foo(InternalState is) { this.is = is; } @Serializer private open pattern serialize(InternalState is) { is = this.is; } } 

هنا ، يتم تمييز المتسلسلين و deserializers بالكلمة الأساسية المفتوحة ، مما يجعلها مفتوحة لضبط الوصول إليها.

وبالتالي ، فإن النهج الجديد يختلف اختلافًا جوهريًا عن النهج القديم: حيث يتم تصميم الفصول الدراسية على أنها قابلة للتسلسل ولا تُعطى للمنصة كما هي. يتطلب ذلك بذل جهد إضافي ، ولكن يجعل إجراء تسلسل أكثر قابلية للتنبؤ به وأكثر أمانًا ومستقلًا عن تنسيق الترميز وإطار عمل التسلسل.

PS الأصدقاء ، إذا كنت ترغب في تلقي أخبار مماثلة عن جافا بسرعة أكبر ، ثم الاشتراك في قناتي في Telegram.

Source: https://habr.com/ru/post/ar455880/


All Articles