Biesterfeld: Sessions und Serialisierbarkeit

Hej,

hab selten in Java so wenig verstanden wie im Moment:

Ausgangspunkt war, dass ich mit JSPs und Sessions arbeiten möchte. Zu meinem Erschrecken müssen aber die Attribute die ich in meine Session stecken möchte Serialisierbar sein. Das obwohl das Interface HttpSession dies an keiner Stelle vorschreibt. Daher die Frage: Kann ich das in irgendeinerweise umgehen? Kann man an irgendeiner Stelle auswählen, dass die Session-Attribute nicht Serialisierbar sein müssen? Die Sessions sind kurz, die Objekte viel zu tief verzweigt, als dass ein unproblematisches Serialisiern möglich wäre und wenn der Server neugestartet wird, ist die Session eben weg.

Nun gut, für den Fall dass das nicht gehen sollte, habe ich mich mal versucht in das Konzept der Serialisierung einzuarbeiten. Die Klasse die serialisiert werden müsste, verfügt bereits über Methoden sich als (xml-)Datei zu schreiben und eine weitere Klasse besteht die aus der xml-Datei wieder exakt die richtige Objektstruktur herzustellen. Also dachte ich mir dass ich durch implementieren der Methoden

  
private void writeObject(java.io.ObjectOutputStream out)  
     throws IOException  
private void readObject(java.io.ObjectInputStream in)  
     throws IOException, ClassNotFoundException;  

das serialisieren über die bereits implementierte Funktionalität bereitstelle (beschrieben in http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html). Das Objekt zu schreiben kann ich noch nachvollziehen, aber wie das Objekt deserialisiert wird versteh ich nicht. Ebensowenig, wie dass readObject() nicht statisch ist und keinen Rückgabewert hat.

Also wenn mir jemand entweder zum Session-Problem oder Serialisierungsproblem nen Tip geben könnte wäre ich sehr dankbar.

Beste Grüße
Biesterfeld

--
Art.1: Et es wie et es
Art.2: Et kütt wie et kütt
Art.3: Et hätt noch immer jot jejange
Das Kölsche Grundgesetz
  1. Hallo Biesterfeld,

    Kann ich das in irgendeinerweise umgehen?

    Damit kenne ich mich nicht aus. Es könnte sein, dass das implementierungsabhängig ist. Vernünftige Datenobjekte sollten aber sehr leicht serialisierbar sein.

    Um ein Objekt serialisierbar zu machen, genügt es schon, Serializable zu implementieren und keine Eigenschaften als final zu deklarieren.
    Zusätzlich ist es noch sinnvoll die Konstante serialVersionUID zu deklarieren um eine Version der Klasse anzugeben und nicht zu speichernde Eigenschaften als transient zu deklarieren.
    Eine Implementierung von readObject und wirteObject ist nicht notwendig, so lange das Standardverfahren ausreicht. Wenn Du das irgendwie erweitern willst um z.B. irgend welche als transient markierten Attribute beim deserialisieren neu zu berechnen, musst Du darauf achten, dass Du dann die Standardverfahren explizit aufrufen musst.

    Die Objekte nach XML zu serialisieren ist keine gute Idee. Ein ObjectStream erlaubt es, direkt Objekte, Arrays usw. auszugeben. Dadurch wird der Implementierung erlaubt, diese Datenstrukturen in einem optimierten Format zu speichern. Wenn Du erst mal XML daraus machst, ist diese Möglichkeit weg.
    Wenn Du keinen sehr guten Grund hast, solltest Du die Methoden nicht implementieren

    Ebensowenig, wie dass readObject() nicht statisch ist und keinen Rückgabewert hat.

    Wenn Du direkt mit ObjectStreams arbeiten würdest, würde das so ablaufen:

    Du erstellst einen ObjectOutputStream und schreibst ein Objekt mit writeObject(Object). Java wird dann irgendwelche Metadaten des Objekts über den ObjectOutputStream ausgeben und für alle referenzierten Objekte rekursiv  wieder writeObject(Object) aufrufen. Irgendwie werden dabei auch Zyklen abgefangen.
    Falls writeObject(Stream) implementiert ist, wird Java die Serialisierung der Eigenschaften nicht automatisch durchführen, sondern nur wirteObject aufrufen. Das kann dann einige Daten in den ObjectStream ausgeben.

    Wenn nun deserialisiert wird, würdest Du einen ObjectInputStream erstellen und mit Object readObject() das erste Objekt lesen. Java würde dieses Objekt mit hilfe der Daten, die es vom Stream liest, erzeugen und wiederum für alle referenzierten Objekte diese mit readObject einlesen.
    Wenn readObject(Stream) implementiert ist, wird das aufgerufen und muss nun genau die Daten einlesen, die readObject vorher ausgegeben hat.
    readObject(Stream) erzeugt also nicht das Objekt sondern stellt nur Werte seiner Eigenschaften wieder her.

    Wichtig ist, dass die Objektstruktur genau in der gleichen Reihenfolge wie beim Serialisieren abgearbeitet wird. Wenn man readObject und writeObject implementiert, muss man darauf natürlich auchten.

    Statt zu beschreiben, wie man ein Objekt serialisiert, kann es einfacher sein, einfach ein einfaches anderes Objekt zu erzeugen, und dieses stattdessen zu serialisieren. (Siehe writeReplace und readResolve)
    Das ist außerdem notwendig, wenn man die Serialisierung lange speichern will und alte Serialisierungen nach Änderung der internen Objektstruktur wieder lesen können muss.

    Grüße

    Daniel

    1. Hej Daniel,

      Kann ich das in irgendeinerweise umgehen?
      Damit kenne ich mich nicht aus. Es könnte sein, dass das implementierungsabhängig ist. Vernünftige Datenobjekte sollten aber sehr leicht serialisierbar sein.

      Geht so, ich möchte in meine Session zwei Objekte stecken. Beide sind Typen von der gleichen Superklasse. Diese kassiert wiederrum Objekte in verschiedenen Collections-Implementierungen, die zudem auch noch in beiden erstgenannten Klassen identisch sein müssen, also stark vereinfacht so:

      -------------------    -----------------------    -------------------
       | class C         |    | class R             |    | class P         |
       -------------------    -----------------------    -------------------
       | final Set<P> ps |    | final Map<P,Long> m |    | final String id |
       | final Set<R> rs |    -----------------------    -------------------
       -------------------
                    |  |_____________
                    |                |
                    v                v
                 ---------------  ----------------
                 |  class A    |  |  class B     |
                 |   extends C |  |    extends C |
                 ---------------  ----------------

      Also A und B teilen sich gleiche Instanzen von P und R. Deswegen kann ich eben nicht einfach A und B serialisierbar machen, wenn Sichergestellt sein muss, dass sie nach dem Deserialisieren auf gleiche Objekte zugreifen.

      Um ein Objekt serialisierbar zu machen, genügt es schon [...] keine Eigenschaften als final zu deklarieren.

      Das wäre der Gau, allerdings würde das die Funktionsweise von readObject( ObjectInputStream) erklären. Ich würde also erst eine neue "leere" Instanz erzeugen und dann mit readObject, lediglich die Felder füllen?

      Die Objekte nach XML zu serialisieren ist keine gute Idee. Ein ObjectStream erlaubt es, direkt Objekte, Arrays usw. auszugeben. Dadurch wird der Implementierung erlaubt, diese Datenstrukturen in einem optimierten Format zu speichern. Wenn Du erst mal XML daraus machst, ist diese Möglichkeit weg.

      Meine gesamte Datenstruktur baut auf einem verbreitetem XML-Schema auf, weshalb ich eben  Methoden und Klassen zum Speichern und Lesen sowieso schon implementiert habe.

      Statt zu beschreiben, wie man ein Objekt serialisiert, kann es einfacher sein, einfach ein einfaches anderes Objekt zu erzeugen, und dieses stattdessen zu serialisieren. (Siehe writeReplace und readResolve)

      Das muss ich mir nochmal anschauen. Vielleicht könnte das helfen. Meine letzte Idee war jetzt um A und B einen Wrapper zu bauen der Serialisierbar ist und somit wenigstens das Problem der gemeinsamen Objekte umgeht. Aber dass die einzelnen Felder der enthaltenen Klassen nicht final sein dürfen hör ich jetzt zum ersten mal und daran wird wohl so einiges scheitern ... Scheint fast so, als wenn das einfachste wäre auf die Session-Implementierung zu verzichten und diese selber "nachzubauen".

      Dank Dir in jedem Fall für Deine ausführliche Beschreibung.

      Beste Grüße
      Biesterfeld

      --
      Art.1: Et es wie et es
      Art.2: Et kütt wie et kütt
      Art.3: Et hätt noch immer jot jejange
      Das Kölsche Grundgesetz
      1. Hallo Biesterfeld,

        Also A und B teilen sich gleiche Instanzen von P und R. Deswegen kann ich eben nicht einfach A und B serialisierbar machen, wenn Sichergestellt sein muss, dass sie nach dem Deserialisieren auf gleiche Objekte zugreifen.

        Doch das solltest Du können. Wenn Du zwei Objekte hast, die identische Objekte referenzieren, dann wird diese Struktur nach dem deserialisieren auch wieder hergestellt. Vorraussetzung ist natürlich, dass die Objekte gemeinsam serialisiert werden. Ich würde aber erwarten, dass das für die in einer Session gespeicherten Objekte gilt. Eine andere Implementierung würde ja viele Datenstrukturen zerstören.

        Das muss ich mir nochmal anschauen. Vielleicht könnte das helfen. Meine letzte Idee war jetzt um A und B einen Wrapper zu bauen der Serialisierbar ist und somit wenigstens das Problem der gemeinsamen Objekte umgeht. Aber dass die einzelnen Felder der enthaltenen Klassen nicht final sein dürfen hör ich jetzt zum ersten mal und daran wird wohl so einiges scheitern ...

        Das liegt schlicht daran, dass Java das Objekt erst instanzieren muss und anschließend die Eigenschaften initialisiert. Sind sie final, geht das nicht mehr.
        Mittels writeReplace und readResolve solltest Du das umgehen können, weil Du dann das zu erzeugende Objekt in readResolve selbst instanzierst.
        Aber müssen diese Eigenschaften denn unbedingt final sein?

        Grüße

        Daniel

        1. Hej,

          Wenn Du zwei Objekte hast, die identische Objekte referenzieren, dann wird diese Struktur nach dem deserialisieren auch wieder hergestellt. Vorraussetzung ist natürlich, dass die Objekte gemeinsam serialisiert werden.

          Das war es, was ich mit dem Session-Wrapper meinte.

          Aber müssen diese Eigenschaften denn unbedingt final sein?

          Müssen nicht. Aber das ist sowieso etwas, was ich mich immerwieder frage: Wann final nd wann nicht? Was mich am allermeisten an dem ganzen Konzept stört ist, dass der HashCode eines Objektes in der nativen Implementierung abhängig vom Zustand der assoziierten Felder ist, also ändere ich ein Feld, ändert sich auch der HashCode. Liegt das Objekt zu dem Zeitpunkt zum Beispiel in einem HashSet "ist das weitere Verhalten nicht absehbar"(TM). Deswegen mach ich es inzwischen so, dass ich hashCode() überschreibe und den Wert selber aus Feldern berechne die ich aber vorher final gesetzt habe.

          Naja, soweit, ich schau mir jetzt erstmal write/readReplace an.

          Grüße
          Biesterfeld

          --
          Art.1: Et es wie et es
          Art.2: Et kütt wie et kütt
          Art.3: Et hätt noch immer jot jejange
          Das Kölsche Grundgesetz
          1. Hallo Biesterfeld,

            Das war es, was ich mit dem Session-Wrapper meinte.

            Ok, allerdings solltest Du dazu keinen brauchen. Probier erst mal aus, ob es auch so klappt. Du kannst das ja einfach Testen, indem Du für zwei Parameter der Session das selbe Objekt angibst und hinterher mit == prüfst, ob die Parameter noch identische Werte haben.

            Müssen nicht. Aber das ist sowieso etwas, was ich mich immerwieder frage: Wann final und wann nicht?

            Auf jeden Fall braucht man final, wenn die Eigenschaften nicht private sind. Sonst braucht man das nicht unbedingt und der vorteil ist nur, dass man die Eigenschaften nicht irgendwo doch versehentlich ändert, obwohl das nicht so vorgesehen war. Wenn man darauf Wert legt, muss man für die Serialisierung eben etwas mehr Aufwand betreiben.

            Was mich am allermeisten an dem ganzen Konzept stört ist, dass der HashCode eines Objektes in der nativen Implementierung abhängig vom Zustand der assoziierten Felder ist, also ändere ich ein Feld, ändert sich auch der HashCode.

            Nein, die Standardimplementierung von hashCode gibt einen eindeutigen Wert für jedes Objekt zurück, der sich für ein Objekt niemals ändert. Er ändert sich natürlich beim Deserialisieren, weil die Objekte dort neu erzeugt werden. Die Implementierung von HashMap u.ä. kann aber sicher damit umgehen.
            Wenn Du irgendwo direkt mit Hashcodes arbeitest, musst Du das natürlich beachten.

            Es gibt Objekte, bei denen der HashCode von veränderbaren Eigenschaften abhängt (ich meine bei StringBuffer ist das z.B. der Fall). Da muss man natürlich etwas aufpassen.

            Grüße

            Daniel