JavaRush /Java-Blog /Random-DE /Externalisierbare Schnittstelle in Java

Externalisierbare Schnittstelle in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute werden wir unsere Einführung in die Serialisierung und Deserialisierung von Java-Objekten fortsetzen. In der letzten Vorlesung haben wir die Serializable- Marker-Schnittstelle kennengelernt , uns Anwendungsbeispiele angeschaut und auch gelernt, wie man den Serialisierungsprozess mit dem Schlüsselwort transient steuert . Nun, „den Prozess verwalten“ ist natürlich ein starkes Wort. Wir haben ein Schlüsselwort, eine Versions-ID, und das ist im Grunde alles. Der Rest des Prozesses ist in Java „fest verdrahtet“ und es besteht kein Zugriff darauf. Aus Bequemlichkeitsgründen ist das natürlich gut. Aber ein Programmierer sollte sich bei seiner Arbeit nicht nur auf sein eigenes Wohlbefinden konzentrieren, oder? :) Es sind noch andere Faktoren zu berücksichtigen. Daher ist Serializable nicht das einzige Tool zur Serialisierung/Deserialisierung in Java. Heute machen wir uns mit der Externalizable- Schnittstelle vertraut . Aber noch bevor wir mit dem Studium begonnen haben, haben Sie vielleicht eine berechtigte Frage: Warum brauchen wir ein weiteres Tool? SerializableIch habe meine Arbeit gemeistert und die automatische Umsetzung des gesamten Prozesses kann mich nur freuen. Auch die Beispiele, die wir uns angesehen haben, waren nicht kompliziert. Also, was ist der Deal? Warum eine andere Schnittstelle für im Wesentlichen dieselbe Aufgabe? Tatsache ist, dass Serializablees eine Reihe von Nachteilen hat. Lassen Sie uns einige davon auflisten:
  1. Leistung. Die Schnittstelle hat Serializableviele Vorteile, aber hohe Leistung gehört eindeutig nicht dazu.

Einführung in die externalisierbare Schnittstelle – 2

Erstens generiert der interne Mechanismus Serializablewährend des Betriebs eine große Menge an Serviceinformationen und verschiedene Arten temporärer Daten.
Zweitens (Sie müssen jetzt nicht näher darauf eingehen und es in Ruhe lesen, wenn Sie interessiert sind) Serializablebasiert die Arbeit auf der Verwendung der Reflection API. Mit diesem Gerät können Sie Dinge tun, die in Java unmöglich erscheinen würden: zum Beispiel die Werte privater Felder ändern. JavaRush hat einen hervorragenden Artikel über die Reflection API , den Sie hier lesen können.

  1. Flexibilität. Wir kontrollieren den Serialisierungs-Deserialisierungsprozess überhaupt nicht, wenn wir die verwenden Serializable.

    Einerseits ist das sehr praktisch, denn wenn uns die Leistung nicht wirklich am Herzen liegt, erscheint die Möglichkeit, keinen Code zu schreiben, praktisch. Aber was ist, wenn wir der Serialisierungslogik wirklich einige unserer eigenen Funktionen hinzufügen müssen (ein Beispiel dafür finden Sie weiter unten)?

    Im Wesentlichen müssen wir zur Steuerung des Prozesses nur ein Schlüsselwort eingeben, transientum einige Daten auszuschließen, und das ist alles. So etwas wie ein „Toolkit“ :/

  2. Sicherheit. Dieser Punkt folgt teilweise aus dem vorherigen.

    Wir haben darüber noch nicht viel nachgedacht, aber was ist, wenn einige Informationen in Ihrem Unterricht nicht für „die Ohren“ (genauer: die Augen) anderer Leute gedacht sind? Ein einfaches Beispiel ist ein Passwort oder andere persönliche Benutzerdaten, die in der modernen Welt durch eine Reihe von Gesetzen geregelt sind.

    Mit Serializablekönnen wir eigentlich nichts dagegen tun. Wir serialisieren alles so wie es ist.

    Aber im positiven Sinne müssen wir diese Art von Daten verschlüsseln, bevor wir sie in eine Datei schreiben oder über das Netzwerk übertragen. Aber Serializablees gibt diese Gelegenheit nicht.

Einführung in die externalisierbare Schnittstelle – 3Nun wollen wir endlich sehen, wie eine Klasse aussehen würde, die 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;

   //...конструктор, геттеры, сеттеры, toString()...

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

   }

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

   }
}
Wie Sie sehen, haben wir erhebliche Änderungen vorgenommen! Der wichtigste liegt auf der Hand: Bei der Implementierung einer Schnittstelle Externalizablemüssen Sie zwei obligatorische Methoden implementieren – writeExternal()und readExternal(). Wie bereits erwähnt, liegt die gesamte Verantwortung für die Serialisierung und Deserialisierung beim Programmierer. Jetzt können Sie jedoch das Problem der mangelnden Kontrolle über diesen Prozess lösen! Der gesamte Prozess wird direkt von Ihnen programmiert, wodurch natürlich ein viel flexiblerer Mechanismus entsteht. Darüber hinaus ist auch das Sicherheitsproblem gelöst. Wie Sie sehen, haben wir in unserer Klasse ein Feld: personenbezogene Daten, die nicht unverschlüsselt gespeichert werden können. Jetzt können wir problemlos Code schreiben, der diese Einschränkung erfüllt. Fügen Sie unserer Klasse beispielsweise zwei einfache private Methoden zum Verschlüsseln und Entschlüsseln geheimer Daten hinzu. Wir schreiben sie in eine Datei und lesen sie verschlüsselt aus der Datei. Und wir werden den Rest der Daten so schreiben und lesen, wie sie sind :) Als Ergebnis wird unsere Klasse in etwa so aussehen:
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 ObjectOutput outund als Parameter verwenden ObjectInput, die wir bereits in der Vorlesung über kennengelernt haben Serializable. Zum richtigen Zeitpunkt verschlüsseln oder entschlüsseln wir die notwendigen Daten und verwenden sie in dieser Form zur Serialisierung unseres Objekts. Mal sehen, wie das in der Praxis aussehen wird:
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("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
In den Methoden encryptString()und decryptString()haben wir speziell eine Ausgabe in die Konsole hinzugefügt, um zu prüfen, in welcher Form die geheimen Daten geschrieben und gelesen werden. Der obige Code gibt die folgende Zeile an die Konsole aus: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Verschlüsselung erfolgreich! Der vollständige Inhalt der Datei sieht folgendermaßen aus: ¬í sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Versuchen wir nun, die von uns geschriebene 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 es nichts Kompliziertes zu geben, es sollte funktionieren! Lassen Sie uns ausführen ... Ausnahme im Thread „main“ java.io.InvalidClassException: UserInfo; Kein gültiger Konstruktor Einführung in die externalisierbare Schnittstelle – 4 Hoppla :( Es stellte sich heraus, dass es nicht so einfach war! Der Deserialisierungsmechanismus löste eine Ausnahme aus und erforderte, dass wir einen Standardkonstruktor erstellten. Ich frage mich, warum? SerializableWir kamen ohne aus... :/ Hier kommen wir zu einer weiteren wichtigen Nuance . Der Unterschied zwischen Serializableund Externalizableliegt nicht nur im „erweiterten“ Zugriff für den Programmierer und der Möglichkeit, den Prozess flexibler zu verwalten, sondern auch im Prozess selbst. Erstens im Deserialisierungsmechanismus ... Wenn SerializableSpeicher verwendet wird, ist er einfach für ein Objekt zugewiesen, woraufhin Werte aus dem Stream gelesen werden, die alle seine Felder füllen. Wenn wir verwenden Serializable, wird der Objektkonstruktor nicht aufgerufen! Die gesamte Arbeit wird durch Reflektion (Reflection API, die wir im letzten Abschnitt kurz erwähnt haben) erledigt Vorlesung). Im Fall von Externalizablewird der Deserialisierungsmechanismus anders sein. Zu Beginn wird der Standardkonstruktor aufgerufen. Und erst dann UserInfowird die Methode des erstellten Objekts aufgerufen readExternal(), die für das Ausfüllen der Felder des Objekts verantwortlich ist. Das heißt Warum muss jede Klasse, die die Schnittstelle implementiert, Externalizableeinen Standardkonstruktor haben ? Fügen wir es unserer Klasse hinzu UserInfound führen Sie 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 Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Passdaten von Ivan Ivanov'} Eine ganz andere Sache! Zuerst wurde der entschlüsselte String mit geheimen Daten an die Konsole ausgegeben und dann wurde unser Objekt aus der Datei im String-Format wiederhergestellt! So haben wir alle Probleme erfolgreich gelöst :) Das Thema Serialisierung und Deserialisierung scheint einfach zu sein, aber wie Sie sehen, waren unsere Vorträge langwierig. Und das ist nicht alles! Bei der Verwendung jeder dieser Schnittstellen gibt es noch viele weitere Feinheiten, aber damit Ihr Gehirn jetzt nicht vor der Menge an neuen Informationen explodiert, werde ich noch kurz einige weitere wichtige Punkte auflisten und Links zu weiterführender Lektüre bereitstellen. Was müssen Sie sonst noch wissen? Achten Sie beim Serialisieren (egal ob Sie oder verwenden) zunächst auf die Variablen . Bei Verwendung werden diese Felder überhaupt nicht serialisiert (und ihr Wert ändert sich dementsprechend nicht, da die Felder zur Klasse und nicht zum Objekt gehören). Aber wenn Sie es verwenden, steuern Sie den Prozess selbst, sodass dies technisch möglich ist. Es wird jedoch nicht empfohlen, da dies mit subtilen Fehlern behaftet ist. Zweitens sollte auch auf Variablen mit dem Modifikator geachtet werden . Wenn sie verwendet werden, werden sie wie üblich serialisiert und deserialisiert, aber wenn sie verwendet werden, ist es unmöglich, eine Variable zu deserialisieren ! Der Grund ist einfach: Alle -Felder werden initialisiert, wenn der Standardkonstruktor aufgerufen wird, und danach kann ihr Wert nicht mehr geändert werden. Verwenden Sie daher zum Serialisieren von Objekten, die -Felder enthalten, die Standard-Serialisierung über . Drittens müssen bei Verwendung der Vererbung alle erbenden Klassen, die von einer Klasse abstammen, auch über Standardkonstruktoren verfügen. Hier sind einige Links zu guten Artikeln über Serialisierungsmechanismen: SerializableExternalizablestaticSerializablestaticExternalizablefinalSerializableExternalizablefinalfinalfinalSerializableExternalizable Auf Wiedersehen! :) :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION