Serialization etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Serialization etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

5 Eylül 2019 Perşembe

Binary Custom Serialization

Giriş
Serializable arayüzünün davranışını değiştirmek yani Custom Serialization mümkün. Açıklaması şöyle.
Serializable classes that want to customize or add some additional logic to enhance the normal serialization/deserialization process should provide writeObject and readObject methods...
Yani eğer Serializble arayüzünün davranışını değiştirmek istersek şu metodları override etmek gerekir.
private void writeObject(java.io.ObjectOutputStream out)
 throws IOException

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

private void readObjectNoData()
 throws ObjectStreamException;
readObject metodu
readObject() metodu içinde in.defaultReadObject() metodu çağrılır. Açıklaması şöyle.
We still have to use the default serialization logic by calling ObjectOutputStream.defaultWriteObject() and ObjectInputStream.defaultReadObject() from writeObject and readObject methods. And if we do not call these default methods, our object will not be serialized/deserialized.
Ayrıca transient alanlar ilklendirilir.

20 Haziran 2019 Perşembe

Binary Serialization

Giriş
Şu satırı dahil ederiz.
mport java.io.Serializable;
Nasıl Kullanılır
Java'nın kendi içinde serialization yapısı mevcut. Aynı C#'ta olduğu gibi serialize edilecek nesnenin Serializable arayüzünden türemesi gerekiyor. Bu arayüz bir marker interface. Herhangi bir metod içermez.
class MyClass implements Serializable
{
  private static final long serialVersionUID = 12345L;
}
Sınıfı Serializable Yapmadan Önce
İyi düşünmek lazım. Açıklaması şöyle.
"Library designers must think very carefully before publishing a serializable class --- as doing so potentially commits you to maintain compatibility with all the instances that have ever been serialized."
Eleştiriler
Serialization mekanizması için bazı eleştiriler var. Bir tanesi  serialization kodunun içinin bilinmemesi. Açıklaması şöyle. Yani kod magic olarak çalışıyor.
"..the choice to implement serialization by 'magic' rather than giving deconstruction and reconstruction a first-class place in the object model itself."
Bu yüzden, sınıfın constructor,setter(),getter() gibi metodları çağrılmıyor. Açıklaması şöyle.
"Serialization pretends to be a library feature. ... In reality, though, serialization extracts object state and recreate objects via privileged, extralinguistic mechanisms, bypassing constructors and ignoring class and field accessibility."
Exception
Eğer sınıf içinde Serializable arayüzünü gerçekleştirmeyen alanlar varsa şöyle bir exception alırız.
Caused by: java.io.NotSerializableException: java.io.FileOutputStream
Bu durumda ya o alan Serializable arayüzünü gerçekleştirmeli ya da transient olarak işaretlenmeli.

serialVersionUID Alanı
serialVersionUID Alanı yazısına taşıdım.

Kendi Sınıfımdaki static ve transient Alanlar
Bu alanlar serialization işlemine girmezler. Yani yazılıp okunmazlar.

Alanları transient olarak işaretlemek için şöyle yaparız.
private transient FileInputStream fis ; 
private transient FileOutputStream fos ; 
 İstisnai tek bir durum var.
There is just one exception to this rule, and it is when the transient final field member is initialized to a constant expression as those defined in the JLS 15.28. Hence, field members declared this way would hold their constant value expression even after deserializing the object.
Constant expression'dan kasıt ise
A constant expression is an expression denoting a value of primitive type or a String
Yani aşağıdaki gibi tanımlı primitive a aslında serialization işlemine giriyor.
transient final int a = 10;
transient Alanları deserialize işleminden sonra tekrar ilklendirmek gerekir.

Ata Sınıfıtaki Alanlar
Eğer ata sınıf Serializable değilse alanları yazılıp okunmaz. Açıklaması şöyle
... the defaultWriteObject can only write the non-static and non-transient fields of the current class. Once the superclass does not implements the Serializable interface, the fields in the superclass can not be serialized into the stream

Serializable Arayüzünün Davranışını Değiştirmek - Custom Serialization
Binary Custom Serialization yazısına taşıdım.

ObjectStream Bu Metodları Nasıl Çağırır
ObjectStream sınıfı yansıma (reflection) vasıtasıyla bu metodları çağırır.
...
private Method writeObjectMethod;
private Method readObjectMethod;
private Method readObjectNoDataMethod;

...

writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
...
No-Arg Constructor Gerekir mi ?
Bu hep kafa karıştıran bir soru. Bazı cevaplar aşağıda

1. Kalıtım Yoksa Kendi Sınıfıma No-Arg Constructor Gerekir mi ?
Serialize edilen nesne kalıtmıyorsa, no-arg constructor  gerekmez.

2. Kalıtım Varsa Hangi Sınıfa No-Arg Constructor Gerekir?
Eğer kalıtım varsa, hiyerarşide Serializable arayüzünü gerçekleştirmeyen ilk ata sınıfın no-arg constructor metoduna sahip olması gerekir. Sebep olarak ta kalıtılan field'ları ilklendirmek için no-arg constructor'ının çağırılması gösterilmiş. Açıklaması şöyle
During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.
Örneğin nesnemiz direkt Object'ten türüyorsa, Object Serializable arayüzünü gerçekleştirmez ancak no-arg constructor metoduna sahip olduğu için sorun çıkmaz.

3. Deserialize İşleminden Sonra Kendi Nesnemin No-Arg Constructor'ı Çağırılır mı?
Deserialize işleminden sonra kendi nesnemin no-arg constructor metodu çağırılmaz. Ancak yukarıdaki madded belirtildiği gibi ata sınıflarımdan Serializable olmayan nesnenin no-arg constructor metodu çağrılır. Çağırılması isteniyorsa aşağıdaki Serialization Proxy kullanılmalıdır.

Örnek
Elimizde şöyle bir kod olsun. Bar sınıfını i = 20 ile ilklendirip kaydedelim. Daha sonra okuma yapınca i = 10 sonucunu alırız. Çünkü Foo sınıfının no-arg constructor metodu çağrılır ve i 10 yapılır
public class Foo {

  private int i;
  Foo(){
    i=10;
  }
  public void setVal(int i){this.i = i;}
}

class Bar extends Foo implements Serializable {

  private String s;
  private Foo foo;

  Bar(String s) {
    this.s = s;
    setVal(20);
  }
  ...
}
Serialization Proxy Nedir?
Serialization Proxy yazısına taşıdım

Binary Serialization - Serialization Proxy

Giriş
Bu örüntünün kullanılmasının iki sebebi var.
1. Nesnemiz Immutable ise
2. Nesnemiz Serializable olmayan bir başka sınıftan kalıyor ve ata sınıf parameterless (default) constructor sağlamıyorsa.

İlk örüntüyü defa burada gördüm.

Nasıl Çalışır
Java bir nesneyi yazmadan önce writeReplace() metodunun override edilip edilmediğine bakar. Eğer edilmişse writeReplace() metodunun döndürdüğü nesne serialize edilir. Bu nesne bizim Serialization Proxy nesnemizdir.

Okurken, Java yazılmış olan Serialization Proxy  nesnesini okur. İşlemden sonra readResolve () metodunun override edilip edilmediğine bakar. Eğer edilmişse nesnenin - ki bizim Serialization Proxy nesnesidir - readResolve() metodu çağırılır. Bu metod kendi immutable nesnemizi yaratır ve döndürür.

Örnek
Şöyle yaparız.
public class Organization implements Serializable {
  private static final long serialVersionUID = 28484399283838343L;
   
  private Object writeReplace() {
    return new SerializationProxy(this);
  }

  private void readObject(final ObjectInputStream stream) 
  throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
  }

  private static class SerializationProxy implements Serializable {
    private static final long serialVersionUID = 484894399233454L;

    
    private Object readResolve() {
      return new Organization(...);
    }
  }
}