CodeGym /בלוג Java /Random-HE /ממשק להחצנה ב-Java
John Squirrels
רָמָה
San Francisco

ממשק להחצנה ב-Java

פורסם בקבוצה
היי! היום נמשיך להכיר סריאליזציה ודה-סריאליזציה של אובייקטי Java . בשיעור האחרון הכרנו את ממשק ה- Serializable marker, סקרנו דוגמאות לשימוש בו וגם למדנו כיצד ניתן להשתמש במילת המפתח החולפת כדי לשלוט בתהליך ההמשכה. ובכן, האמירה שאנו 'שולטים בתהליך' עשויה להגזים בזה. יש לנו מילת מפתח אחת, מזהה גרסה אחד, וזה בערך הכל. שאר התהליך מוסתר בתוך Java, ואיננו יכולים לגשת אליו. כמובן שמבחינת נוחות זה טוב. אבל מתכנת לא צריך להיות מונחה רק על ידי הנוחות שלו או שלה, נכון? :) ישנם גורמים נוספים שאתה צריך לקחת בחשבון. זו הסיבה ש- Serializable אינו המנגנון היחיד לסריאליזציה-deserialization ב-Java. היום נכיר את הממשק Externalizable . אבל לפני שנתחיל ללמוד את זה, אולי תהיה לך שאלה סבירה: למה אנחנו צריכים מנגנון אחר? Serializableעשה את העבודה שלו, ומה לא לאהוב ביישום האוטומטי של כל התהליך? וגם הדוגמאות שבדקנו היו לא מסובכות. אז מה הבעיה? מדוע אנו זקוקים לממשק נוסף עבור אותן משימות בעצם? העובדה היא Serializableשיש לו מספר חסרונות. אנו מפרטים כמה מהם:
  1. ביצועים. לממשק Serializableיתרונות רבים, אך ברור שביצועים גבוהים אינם אחד מהם.

    היכרות עם הממשק להחצנה - 2

    ראשית, Serializable היישום הפנימי של מייצר כמות גדולה של מידע שירות וכל מיני נתונים זמניים.

    שנית, Serializable מסתמך על ה-API של Reflection (אינך צריך לצלול לעומק על זה כרגע; אתה יכול לקרוא עוד בנוחיות שלך, אם אתה מעוניין). הדבר הזה מאפשר לך לעשות את הדברים הבלתי אפשריים לכאורה ב-Java: למשל, לשנות את הערכים של שדות פרטיים. ל- CodeGym יש מאמר מצוין על ה-Reflection API . אתה יכול לקרוא על זה שם.

  2. גְמִישׁוּת. אנחנו לא שולטים בתהליך הסידרה-דה-סריאליזציה כשאנחנו משתמשים Serializableבממשק.

    מצד אחד, זה מאוד נוח, כי אם אנחנו לא מודאגים במיוחד מהביצועים, אז זה נראה נחמד שלא נצטרך לכתוב קוד. אבל מה אם אנחנו באמת צריכים להוסיף כמה מהתכונות שלנו (נספק דוגמה להלן) ללוגיקת ההסדרה?

    בעיקרון, כל מה שעלינו לשלוט בתהליך הוא transientמילת המפתח לאי הכללה של נתונים מסוימים. זהו זה. זה כל ארגז הכלים שלנו :/

  3. בִּטָחוֹן. פריט זה נובע בחלקו מהפריט הקודם.

    לא בילינו הרבה זמן במחשבה על זה בעבר, אבל מה אם מידע מסוים בכיתה שלך אינו מיועד לעיניים ולאוזניים הסקרניות של אחרים? דוגמה פשוטה היא סיסמה או נתוני משתמש אישיים אחרים, שבעולם של היום נשלטים על ידי שורה של חוקים.

    אם נשתמש ב- Serializable, אנחנו לא באמת יכולים לעשות שום דבר בנידון. אנחנו מסדרים הכל כמו שהוא.

    אבל אם נעשה זאת בדרך הנכונה, עלינו להצפין סוג זה של נתונים לפני כתיבתם לקובץ או שליחתם דרך רשת. אבל Serializableלא מאפשר את זה.

היכרות עם הממשק להחצנה - 3ובכן, בוא נראה סוף סוף איך הכיתה תיראה אם ​​נשתמש בממשק Externalizable.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

private static final long SERIAL_VERSION_UID = 1L;

   // ...constructor, getters, setters, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
כפי שאתה יכול לראות, יש לנו שינויים משמעותיים! העיקרית שבהן ברורה: בעת יישום Externalizableהממשק, עליך ליישם שתי שיטות נדרשות: writeExternal()ו readExternal(). כפי שאמרנו קודם, האחריות לסריאליזציה ולדה-סריאליזציה תהיה על המתכנת. אבל עכשיו אתה יכול לפתור את הבעיה של חוסר שליטה על התהליך! כל התהליך מתוכנת ישירות על ידך. באופן טבעי, זה מאפשר מנגנון הרבה יותר גמיש. בנוסף, הבעיה באבטחה נפתרה. כפי שאתה יכול לראות, לכיתה שלנו יש שדה נתונים אישיים שלא ניתן לאחסן ללא מוצפן. כעת נוכל לכתוב בקלות קוד שעומד באילוץ הזה. לדוגמה, אנו יכולים להוסיף לכיתה שלנו שתי שיטות פרטיות פשוטות להצפנה ולפענוח נתונים רגישים. נכתוב את הנתונים לקובץ ונקרא אותם מהקובץ בצורה מוצפנת. שאר הנתונים ייכתבו ויקראו כמו שהם :) כתוצאה מכך, הכיתה שלנו נראית בערך כך:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

   private static final long serialVersionUID = 1L;

   public UserInfo() {
   }

   public UserInfo(String firstName, String lastName, String superSecretInformation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.superSecretInformation = superSecretInformation;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(this.getFirstName());
       out.writeObject(this.getLastName());
       out.writeObject(this.encryptString(this.getSuperSecretInformation()));
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       firstName = (String) in.readObject();
       lastName = (String) in.readObject();
       superSecretInformation = this.decryptString((String) in.readObject());
   }

   private String encryptString(String data) {
       String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
       System.out.println(encryptedData);
       return encryptedData;
   }

   private String decryptString(String data) {
       String decrypted = new String(Base64.getDecoder().decode(data));
       System.out.println(decrypted);
       return decrypted;
   }

   public String getFirstName() {
       return firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public String getSuperSecretInformation() {
       return superSecretInformation;
   }
}
הטמענו שתי שיטות שמשתמשות באותה ObjectOutputופרמטרים ObjectInputשכבר פגשנו בשיעור על Serializable. ברגע הנכון, אנו מצפינים או מפענחים את הנתונים הנדרשים, ואנחנו משתמשים בנתונים המוצפנים כדי להדגיש את האובייקט שלנו. בוא נראה איך זה נראה בפועל:
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       UserInfo userInfo = new UserInfo("Paul", "Piper", "Paul Piper's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
ב- encryptString()and decryptString(), הוספנו במיוחד פלט מסוף כדי לאמת את הצורה שבה ייכתבו ויקראו הנתונים הסודיים. הקוד למעלה הציג את השורה הבאה: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh ההצפנה הצליחה! התוכן המלא של הקובץ נראה כך: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx עכשיו בואו ננסה להשתמש בלוגיקת הסידריאליזציה שלנו.
public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();

   }
}
ובכן, שום דבר לא נראה מסובך כאן. זה אמור לעבוד! אנו מפעילים אותו ומקבלים... חריגה בשרשור "ראשי" java.io.InvalidClassException: UserInfo; אין בנאי חוקי היכרות עם הממשק להחצנה - 4 אופס! :( מסתבר שזה לא כל כך קל! מנגנון הדה-סריאליזציה זרק חריג ודרש שניצור בנאי ברירת מחדל. מעניין למה. עם , Serializableהסתדרנו בלי אחד... :/ כאן נתקלנו בעוד ניואנס חשוב. ההבדל בין Serializableוטמון Externalizableלא רק בגישה ה'מורחבת' של המתכנת וביכולת לשלוט בצורה גמישה יותר בתהליך, אלא גם בתהליך עצמו. מעל הכל, במנגנון הדה- סריאליזציה . בעת שימוש Serializable, פשוט מוקצה זיכרון לאובייקט, ו לאחר מכן קוראים ערכים מהזרם ומשמשים להגדרת השדות של האובייקט. אם אנו משתמשים Serializable, הבנאי של האובייקט לא נקרא! כל העבודה מתרחשת באמצעות רפלקציה (ה-Reflection API, אותו הזכרנו בקצרה בשיעור האחרון). עם Externalizable, מנגנון הדה-סריאליזציה שונה. הבנאי ברירת המחדל נקרא תחילה. רק לאחר מכן נקראת השיטה UserInfoשל ​​האובייקט שנוצר readExternal(). הוא אחראי על הגדרת השדות של האובייקט. זו הסיבה שכל מחלקה המיישמת את Externalizableהממשק חייבת להיות בעלת בנאי ברירת מחדל . בואו נוסיף אחד UserInfoלכיתה שלנו ונפעיל מחדש את הקוד:
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();
   }
}
פלט מסוף: נתוני הדרכון של פול פייפר UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'נתוני הדרכון של פול פייפר' } עכשיו זה משהו אחר לגמרי! ראשית, המחרוזת המפוענחת עם מידע סודי הוצגה בקונסולה. ואז האובייקט ששחזרנו מהקובץ הוצג כמחרוזת! אז פתרנו בהצלחה את כל הבעיות :) הנושא של סידריאליזציה וסידריאליזציה נראה פשוט, אבל, כפי שאתה יכול לראות, השיעורים היו ארוכים. ויש עוד כל כך הרבה שלא כיסינו! עדיין ישנן דקויות רבות הקשורות בשימוש בכל אחד מהממשקים הללו. אבל כדי להימנע מפיצוץ המוח שלך ממידע חדש מוגזם, אפרט בקצרה עוד כמה נקודות חשובות ואתן לך קישורים לקריאה נוספת. אז מה עוד אתה צריך לדעת? ראשית , במהלך הסידרה (לא משנה אם אתה משתמש Serializableאו Externalizable), שימו לב staticלמשתנים. כאשר אתה משתמש Serializable, שדות אלה אינם מסודרים כלל (ולפיכך, הערכים שלהם אינם משתנים, כי staticשדות שייכים למחלקה, לא לאובייקט). אבל כשאתה משתמש Externalizable, אתה שולט בתהליך בעצמך, כך שמבחינה טכנית אתה יכול לעשות אותם בסידרה. אבל, אנחנו לא ממליצים על זה, מכיוון שעשייה עשויה ליצור הרבה באגים עדינים. שנית , כדאי לשים לב גם למשתנים עם השינוי final. כאשר אתה משתמש Serializable, הם מסודרים ויוצאים בסידרה כרגיל, אבל כשאתה משתמש ב- Externalizable, אי אפשר לבצע דה-סריאליזציה של finalמשתנה ! הסיבה פשוטה: כל finalהשדות מאותחלים כאשר נקרא בנאי ברירת המחדל - לאחר מכן, לא ניתן לשנות את הערך שלהם. לכן, כדי לעשות סדרה של אובייקטים שיש להם finalשדות, השתמש בסריאליזציה הסטנדרטית המסופקת על ידי Serializable. שלישית , כאשר אתה משתמש בירושה, כל המחלקות הצאצאיות שיורשות Externalizableמחלקה כלשהי חייבות להיות גם בוני ברירת מחדל. הנה קישור למאמר טוב על מנגנוני סדרה: עד הפעם הבאה! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION