CodeGym/Java-Blog/Random-DE/Externalisierbare Schnittstelle in Java
Autor
Volodymyr Portianko
Java Engineer at Playtika

Externalisierbare Schnittstelle in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute lernen wir weiterhin die Serialisierung und Deserialisierung von Java- Objekten kennen. In der letzten Lektion haben wir die Serializable- Marker-Schnittstelle kennengelernt , Beispiele für ihre Verwendung besprochen und außerdem gelernt, wie Sie das Schlüsselwort transient verwenden können , um den Serialisierungsprozess zu steuern. Nun ja, zu sagen, dass wir „den Prozess kontrollieren“, ist vielleicht übertrieben. Wir haben ein Schlüsselwort, eine Versionskennung und das war’s auch schon. Der Rest des Prozesses ist in Java verborgen und wir können nicht darauf zugreifen. Aus Komfortgründen ist das natürlich gut. Aber ein Programmierer sollte sich nicht nur von seinem eigenen Komfort leiten lassen, oder? :) Es gibt noch andere Faktoren, die Sie berücksichtigen müssen. Deshalb Serialisierbarist nicht der einzige Mechanismus zur Serialisierung/Deserialisierung in Java. Heute machen wir uns mit der Externalizable- Schnittstelle vertraut. Aber bevor wir mit der Untersuchung beginnen, haben Sie vielleicht eine berechtigte Frage: Warum brauchen wir einen anderen Mechanismus? Serializablehat seinen Job gemacht, und was kann man an der automatischen Implementierung des gesamten Prozesses nicht lieben? Und auch die Beispiele, die wir uns angeschaut haben, waren unkompliziert. Also, was ist das Problem? Warum brauchen wir für im Wesentlichen dieselben Aufgaben eine andere Schnittstelle? Tatsache ist, dass es Serializablemehrere Mängel aufweist. Wir listen einige davon auf:
  1. Leistung. Die SerializableSchnittstelle hat viele Vorteile, aber hohe Leistung gehört eindeutig nicht dazu.

    Einführung in die Externalizable-Schnittstelle – 2

    Erstens Serializable generiert die interne Implementierung eine große Menge an Serviceinformationen und alle Arten von temporären Daten.

    Zweitens Serializable basiert es auf der Reflection-API (Sie müssen sich jetzt nicht intensiv damit befassen; bei Interesse können Sie in Ruhe mehr darüber lesen). Mit diesem Ding können Sie die scheinbar unmöglichen Dinge in Java tun: zum Beispiel die Werte privater Felder ändern. CodeGym hat einen hervorragenden Artikel über die Reflection API . Dort können Sie darüber nachlesen.

  2. Flexibilität. Wir kontrollieren den Serialisierungs-Deserialisierungsprozess nicht, wenn wir die SerializableSchnittstelle verwenden.

    Einerseits ist es sehr praktisch, denn wenn uns die Leistung nicht besonders wichtig ist, scheint es schön, keinen Code schreiben zu müssen. Aber was ist, wenn wir der Serialisierungslogik wirklich einige unserer eigenen Funktionen hinzufügen müssen (wir geben unten ein Beispiel)?

    Im Grunde müssen wir den Prozess nur mit dem transientSchlüsselwort steuern, um einige Daten auszuschließen. Das ist es. Das ist unser gesamter Werkzeugkasten :/

  3. Sicherheit. Dieser Artikel leitet sich teilweise vom vorherigen Artikel ab.

    Wir haben bisher nicht viel Zeit damit verbracht, darüber nachzudenken, aber was ist, wenn einige Informationen in Ihrem Unterricht nicht für die neugierigen Augen und Ohren anderer bestimmt sind? Ein einfaches Beispiel ist ein Passwort oder andere persönliche Benutzerdaten, die heutzutage einer Reihe von Gesetzen unterliegen.

    Wenn wir verwenden Serializable, können wir eigentlich nichts dagegen tun. Wir serialisieren alles so, wie es ist.

    Aber wenn wir es richtig machen, müssen wir diese Art von Daten verschlüsseln, bevor wir sie in eine Datei schreiben oder über ein Netzwerk senden. Macht das aber Serializablenicht möglich.

Einführung in die Externalizable-Schnittstelle – 3Nun wollen wir endlich sehen, wie die Klasse aussehen würde, wenn wir die ExternalizableSchnittstelle verwenden würden.
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 {

   }
}
Wie Sie sehen, gibt es bei uns erhebliche Veränderungen! Der wichtigste liegt auf der Hand: Bei der Implementierung der ExternalizableSchnittstelle müssen Sie zwei erforderliche Methoden implementieren: writeExternal()undreadExternal(). Wie bereits erwähnt, liegt die Verantwortung für die Serialisierung und Deserialisierung beim Programmierer. Aber jetzt können Sie das Problem der fehlenden Kontrolle über den Prozess lösen! Der gesamte Prozess wird direkt von Ihnen programmiert. Dies ermöglicht natürlich einen wesentlich flexibleren Mechanismus. Darüber hinaus ist das Problem mit der Sicherheit gelöst. Wie Sie sehen, verfügt unsere Klasse über ein persönliches Datenfeld, das nicht unverschlüsselt gespeichert werden kann. Jetzt können wir problemlos Code schreiben, der diese Einschränkung erfüllt. Beispielsweise können wir unserer Klasse zwei einfache private Methoden zum Ver- und Entschlüsseln sensibler Daten hinzufügen. Wir schreiben die Daten in die Datei und lesen sie verschlüsselt aus der Datei. Der Rest der Daten wird so wie sie sind geschrieben und gelesen :) Als Ergebnis sieht unsere Klasse in etwa so aus:
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;
   }
}
Wir haben zwei Methoden implementiert, die dieselben Parameter ObjectOutputund ObjectInputParameter verwenden, die wir bereits in der Lektion kennengelernt haben Serializable. Im richtigen Moment verschlüsseln oder entschlüsseln wir die erforderlichen Daten und verwenden die verschlüsselten Daten, um unser Objekt zu serialisieren. Mal sehen, wie das in der Praxis aussieht:
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();

   }
}
In den Methoden encryptString()und decryptString()haben wir speziell eine Konsolenausgabe hinzugefügt, um die Form zu überprüfen, in der die geheimen Daten geschrieben und gelesen werden. Der obige Code zeigte die folgende Zeile an: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Die Verschlüsselung war erfolgreich! Der vollständige Inhalt der Datei sieht folgendermaßen aus: ¬í sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Versuchen wir nun, unsere Deserialisierungslogik zu verwenden.
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();

   }
}
Nun, hier scheint nichts kompliziert zu sein. Es sollte funktionieren! Wir führen es aus und erhalten ... Ausnahme im Thread „main“ java.io.InvalidClassException: UserInfo; Kein gültiger Konstruktor Einführung in die Externalizable-Schnittstelle – 4 Ups! :( Anscheinend ist es nicht so einfach! Der Deserialisierungsmechanismus hat eine Ausnahme ausgelöst und verlangt, dass wir einen Standardkonstruktor erstellen. Ich frage mich, warum. Mit sind Serializablewir ohne ausgekommen... :/ Hier sind wir auf eine weitere wichtige Nuance gestoßen. Die Der Unterschied zwischen Serializableund Externalizableliegt nicht nur im „erweiterten“ Zugriff des Programmierers und der Möglichkeit, den Prozess flexibler zu steuern, sondern auch im Prozess selbst. Vor allem im Deserialisierungsmechanismus . Bei der VerwendungSerializable, wird dem Objekt einfach Speicher zugewiesen, und dann werden Werte aus dem Stream gelesen und zum Festlegen der Felder des Objekts verwendet. Wenn wir verwenden Serializable, wird der Konstruktor des Objekts nicht aufgerufen! Die gesamte Arbeit geschieht durch Reflektion (die Reflection API, die wir in der letzten Lektion kurz erwähnt haben). Bei Externalizableist der Deserialisierungsmechanismus anders. Zuerst wird der Standardkonstruktor aufgerufen. Erst danach wird die Methode des erstellten UserInfoObjekts readExternal()aufgerufen. Es ist für das Festlegen der Felder des Objekts verantwortlich. Aus diesem Grund muss jede Klasse, die die ExternalizableSchnittstelle implementiert, über einen Standardkonstruktor verfügen . Fügen wir einen zu unserer UserInfoKlasse hinzu und führen den Code erneut aus:
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();
   }
}
Konsolenausgabe: Passdaten von Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Passdaten von Paul Piper' } Das ist etwas ganz anderes! Zunächst wurde die entschlüsselte Zeichenfolge mit geheimen Informationen auf der Konsole angezeigt. Dann wurde das aus der Datei wiederhergestellte Objekt als Zeichenfolge angezeigt! Wir haben also alle Probleme erfolgreich gelöst :) Das Thema Serialisierung und Deserialisierung scheint einfach, aber wie Sie sehen, waren die Lektionen langwierig. Und es gibt noch so viel mehr, was wir noch nicht abgedeckt haben! Bei der Verwendung jeder dieser Schnittstellen gibt es noch viele Feinheiten. Damit Ihr Gehirn jedoch nicht durch die Fülle an neuen Informationen explodiert, liste ich kurz noch ein paar weitere wichtige Punkte auf und gebe Ihnen Links zu weiterführender Lektüre. Was müssen Sie sonst noch wissen? Achten Sie zunächst während der Serialisierung (unabhängig davon, ob Sie Serializableoder verwenden) auf Variablen. Wenn Sie verwenden , werden diese Felder überhaupt nicht serialisiert (und dementsprechend ändern sich ihre Werte nicht, da Felder zur Klasse und nicht zum Objekt gehören). Aber wenn Sie verwendenExternalizablestaticSerializablestaticExternalizable, steuern Sie den Prozess selbst, sodass Sie sie technisch gesehen serialisieren könnten. Wir empfehlen dies jedoch nicht, da dies wahrscheinlich zu vielen subtilen Fehlern führt. Zweitens sollten Sie auch auf Variablen mit dem Modifikator achten final. Wenn Sie verwenden Serializable, werden sie wie üblich serialisiert und deserialisiert, aber wenn Sie verwenden Externalizable, ist es unmöglich, eine finalVariable zu deserialisieren ! Der Grund ist einfach: Alle finalFelder werden initialisiert, wenn der Standardkonstruktor aufgerufen wird – danach kann ihr Wert nicht mehr geändert werden. Um Objekte mit finalFeldern zu serialisieren, verwenden Sie daher die von bereitgestellte Standardserialisierung Serializable. Drittens : Wenn Sie die Vererbung verwenden, erben alle Nachkommenklassen einige davonExternalizableDie Klasse muss auch über Standardkonstruktoren verfügen. Hier ist ein Link zu einem guten Artikel über Serialisierungsmechanismen: Bis zum nächsten Mal! :) :)
Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare