Home برمجة الـ Binary Serialization في الجافا
الـ Binary Serialization في الجافا

الـ Binary Serialization في الجافا

275
0

السلام عليكم و رحمة الله و بركاته

 

الفهرس :

  1. ما هي عملية الـ Serialization ؟
  2. كيف نقوم بها ؟
  3. مثال تطبيقي
  4. متى نستخدم transient ؟
  5. الواجهة البديلة Externalizable
  6. اختبر قدراتك في الـ Serialization

 

1. ما هي عملية الـ Serialization ؟

الـ Serialization عبارة عن آلية تسمح بحفظ الكائنات في storage medium أي وسيلة تخزين مثل الملفات أو ذاكرات التخزين المؤقت (memory buffer) كما تُمكن أيضا من نقل الكائنات عبر شبكات الاتصال, تم دعمها في الجافا ابتداء من النسخة 1.1 من الـ JDK, تسمح هذه الآلية بالحفاظ على الـ Object Persistence حيث تُنقل الكائنات عبر binary support على شكل مجموعة من الـ bytes أو على شكل قابل للقراءة مثل ملفات XML. الـ format المستخدم لحفظ الكائنات لا علاقة له بنظام التشغيل حيث يُمكن إعادة بناء الكائن (تُسمى هذه العملية بـ deserialization) في نظام تشغيل مختلف عن الذي تمت فيه عملية الـ serialization.

أثناء هذه العملية, يُمكن تخزين الكائنات على القرص الصلب أو في قاعدة بيانات كما يُمكن أيضاً إعادة بنائها في جهاز آخر في الشبكة حيث تتم إعادة إنشائها باستخدام الـ JVM الموجودة في الجهاز المستقبِل. (كما تفعل RMI مثلا)

في هذه المقالة, سنشرح كيفية عمل الـ binary serialization مع العلم أنه يُمكن عمل الـ XML serialization في الجافا باستخدام الفئتين XMLEncoder و XMLDecoder حيث يتم تخزين البيانات بصيغة XML.

 

2. كيف نقوم بالـ Serialization

تعتمد الـ serialization على الـ streams لذا يجب أن تكون لديك معرفة مُسبقة بمعظم الفئات الموجودة في الحزمة java.io و كيفية التعامل معها. تُوفر الـ Java API الأدوات اللازمة لعمل الـ serialization و هي :

  • الواجهة Serializable
  • الفئتين ObjectOutputStream و ObjectInputStream

الواجهة Serializable تسمح بتحديد الفئة التي نُريد عمل serialization لها, الفئتين ObjectOutputStream و ObjectInputStream يُمكّنان (على التوالي) من تحديد كيف ستتم عملية الــ serialization و الـ  deserialization.

 

2.1 – الواجهة Serializable

لعمل serialization لكائن من فئة معينة, يجب على هذه الفئة أن تقوم بعمل implements للواجهة Serializable أو ترث فئة قامت مُسبقاً بعمل implements للواجهة Serializable.

لا تحتوي الواجهة Serializable على أي دالة حيث يقتصر دورها على تحديد فئة معينة كــ serializable.

يتم عمل الـ serialization لكافة حقول الكائن باستثناء تلك الغير serializable أو تلك التي تم الإعلان عنها باستخدام الكلمة static أو transient.

 

ملاحظات :

  • جميع الأنواع الأساسية (مثل int, long, float, …) يُمكن عمل serialization لها.
  • ليست كل الكائنات serializable فقد تكون مرتبطة بنظام التشغيل أو مرتبطة بسياق التنفيذ في الذاكرة مثل الـ Threads.
  • أصبحت File فئة serializable في النسخ الحديثة من جافا.
  • إذا حاولت عمل serialization لكائن من فئة لم تقم بعمل implements للواجهة Serializable, سيتم إصدار استثناء من نوع NotSerializableException.
  • استخدم الأداة Serialver من سطر الأوامر لتحديد هل الفئة تدعم الـ serialization أم لا, أمثلة :
  • الـ serialVersionUID عبارة عن رقم النسخة حيث يوجد في كل فئة قامت بعمل implements للواجهة Serializable, الهدف منه هو ضمان تطابق الفئة عند عمل الـ deserialization, وإذا تغيرت الفئة بين عمليتي الـ serialization و deserialization, يتم إصدار استثناء من نوع InvalidClassException.
  • كل فئة serializable يجب أن تُعلن بشكل صريح عن المتغير serialVersionUID حيث يجب أن يكون final, static و من نوع long أيضاً, مثال :
  • إذا لم تقم الفئة بالإعلان عن الحقل serialVersionUID, تتولى آلية الـ serialization توليد رقم تسلسلي للفئة بشكل تلقائي.

 

2.2 – الفئتان ObjectOutputStream و ObjectInputStream

ورثت الواجهتان ObjectOutput و ObjectInput خصائص الواجهتين DataOutput و  DataInput و أضافتا إمكانية كتابة/قراءة الكائنات, تحتوي كل من DataOutput و  DataInput على دوال تُمكن من كتابة/قراءة الأنواع الأولية (مثل char, long, double, ..) حيث أضافت ObjectOutput و ObjectInput إمكانية كتابة/قراءة المصفوفات و الكائنات. قامت كل من ObjectOutputStream و ObjectInputStream على التوالي بعمل implements لـ ObjectOutput|ObjectInput.

كلا الفئتين ObjectOutputStream و ObjectInputStream عبارة عن stream object, حيث يسمحان بعمل serialization/deserialization لكائن معين, على التوالي باستخدام إحدى الدالتين writeObject/readObject.

 

3. مثال تطبيقي

نبدأ مع الفئة التي سيتم عمل serialization لها :

و هذه الفئة المسئولة عن الـ Serialization :

تستقبل الدالة serializeUser كائناً من نوع User و تُعيد كائناً من نوع File, يُمثل الملف الذي تمت فيه عملية الـ serialization.

في البداية, قمنا بإنشاء الملف الذي سيحوي الـ serialized object (في الحقيقة, الملف سيُخزن فقط حقول الكائن) و باستخدام الفئة FileOutputStream, قمنا بإرفاق stream writing للكائن fichier, ثم قمنا بكتابة بيانات المستخدم u داخل كائن الـ output stream باستخدام الدالة writeObject لنقوم بعد ذلك بإفراغ محتوى الـ oos باستخدام الدالة flush.

مُخرجات الكود :

نأتي الآن إلى الفئة المسئولة عن الـ Deserialization :

في البداية, قمنا بإنشاء كائن من FileInputStream يُمثل stream reading للملف الموجود في المسار chemin ثم قمنا بالإعلان عن كائن من ObjectInputStream من أجل إعادة بناء الكائن u باستخدام الدالة readObject. لاحظ أننا قمنا بعمل casting للحصول على النوع الحقيقي للـ serialized object. بطبيعة الحال, يجب أن تتم الـ deserialization بنفس الفئة التي تمت بها عملية الـ serialization. (بغض النظر عن المفاهيم البرمجية, يُمكننا استنتاج هذه الملاحظة اعتماداً على المنطق السليم).

يتم إصدار استثناء من نوع StreamCorruptedException إذا تم تغيير محتوى الملف من خلال editor مثلا. يُمكن أيضاً إصدار استثناء من نوع ClassNotFoundException إذا تم تحويل الكائن إلى فئة لم يتم العثور عليها أثناء التنفيذ.

 

 

مُخرجات الكود :


4. متى نستخدم transient ؟

يُمكن رؤية محتوى المتغيرات من خلال الـ stream الذي تمت عملية الـ serialization بداخله و بالتالي يُمكن لأي شخص يستطيع الوصول إلى الـ stream, رؤية محتوى مختلف المتغيرات حتى لو كانت private و هذا يُمكن أن يُؤدي إلى وجود العديد من المشاكل التي تُهدد حماية البيانات, خصوصاً عند نقل معلومات حساسة عن طريق الشبكة. لهذا السبب قامت Java بتوفير الكلمة المحجوز transient التي تعني أن المتغير المرافق لها ينبغي عدم إدراجه في عملية الـ serialization و بالتالي الـ deserialization.

للتأكد, يُمكنك تجربة transient مع  المتغير الخاص بكلمة المرور في الفئة User.

أثناء عملية الـ deserialization يتم إسناد القيمة null للمتغيرات التي تم الإعلان عنها باستخدام transient و هذا جيد من ناحية لكن في نفس الوقت قد يؤثر على عمل الدوال التي تستخدم تلك المتغيرات حيث سيتم إصدار استثناء من نوع NullPointerException.

 

 

5. الواجهة البديلة Externalizable

رأينا سابقاً كيفية عمل serialization من خلال كتابة كائن بمجمله في stream و إعادة بنائه من جديد. قد نحتاج في بعض الأحيان إلى عمل الـ serialization لمتغيرات معدودة لكائن معين لذا توفر جافا الواجهة البديلة Externalizable التي تُمكن من السيطرة الكاملة على عملية الـ serialization. (مع العلم أن الواجهة Externalizable قامت بعمل implements للواجهة Serializable)

تحتوي الواجهة البديلة على دالتين هما writeExternal و readExternal, من خلال إعادة تعريفهما سنحدد المتغيرات التي ستدخل في عملية الـ serialization.

وفقاً للحالة الافتراضية للواجهة Externalizable, لا تأخذ عملية الـ serialization في الاعتبار أي من متغيرات الكائن لذا لا فائدة من استخدام transient في هذه الحالة.

عند إعادة تعريف الدالة readExternal ستحتاج إلى استخدام دوال DataInput للأنواع الأولية (مثل char, long, double, ..) و readObject للكائنات (مثل String, Object, Arrays ..). نفس الشيء بالنسبة للدالة writeObject و الواجهة DataOutput مع writeExternal.

 

لنأخذ الفئة User كمثال :

و هذه الفئة المسئولة عن الـ serialization/deserialization :

مُخرجات الكود :

 

6. اختبر قدراتك في الـ Serialization

قم بإنشاء فئة باسم CompteBank تُمكن من التعامل مع حسابات بنكية مع الأخذ بعين الاعتبار حساسية المبالغ المالية للزبناء. يتم تعريف كل حساب بالرقم التسلسلي, الاسم الكامل لصاحب الحساب, رصيد الحساب, بالإضافة إلى الضمان المالي.

تتم عملية الـ serialization في ملف بامتداد ser يُخزن على سطح المكتب. قم بعمل الـ deserialization و أظهر أسماء أصحاب الحسابات.

قم بإنشاء فئة أخرى باسم Client, يتم تعريف كل زبون برقم بطاقة الهوية (IC) بالإضافة إلى الاسم الكامل.

قم بعمل الـ serialization في نفس الملف السابق الذي تم تخزينه على سطح المكتب ثم قم بعمل deserialization لجميع الكائنات الموجودة في الملف و أظهر أسماء الزبناء بجانب الضمان المالي لكل حساب.

 

 

 

 

تحياتي.

(275)

أحمد محمد أحمد محمد ، مهندس برمجيات و طالب دكتوراه في مجال Complex Networks Analysis. مهتم بالبرمجة و دراسة الشبكات.

LEAVE YOUR COMMENT

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

مشاركة