次のように定義されたシングルトンクラスがあるとします。
public class MySingleton implements Serializable{
private static MySingleton myInstance;
private MySingleton(){
}
static{
myInstance =new MySingleton();
}
public static MySingleton getInstance(){
return MySingleton.myInstance;
}
}
私によると、上記の定義はシングルトンの要件を満たしています。追加された唯一の動作は、クラスがシリアル化可能なインターフェイスを実装することです。
別のクラスXがシングルのインスタンスを取得し、それをファイルに書き込み、後で非シリアル化して別のインスタンスを取得する場合、シングルトンの原理に反する2つのインスタンスがあります。
どうすればこれを回避できますか、上記の定義自体が間違っています。
これを行う最良の方法は、列挙型シングルトンパターンを使用することです。
_public enum MySingleton {
INSTANCE;
}
_
これにより、オブジェクトのシングルトン性が保証され、常に同じインスタンスを取得できるようにシリアル化が可能になります。
より一般的には、次のようなreadResolve()
メソッドを提供できます。
_protected Object readResolve() {
return myInstance;
}
_
@ColinDは一種の正しいことですが、彼の答えは、なぜシングルトンがシリアライゼーションに本当に耐えられないのかを示しています。
Enum値をシリアル化すると、次のようになります( here を参照)。
Enumインスタンスをシリアル化するためのルールは、「通常の」シリアル化可能なオブジェクトをシリアル化するためのルールとは異なります。enumインスタンスのシリアル化された形式は、その列挙定数名とベース列挙型を識別する情報のみで構成されます。逆シリアル化の動作も異なります。適切な列挙クラスを見つけるためにクラス情報が使用され、返される列挙定数を取得するために、そのクラスと受け取った定数名でEnum.valueOfメソッドが呼び出されます。
したがって、enum値に付加する追加の状態は、シリアル化と逆シリアル化に耐えられません。
カスタムシリアライゼーション/デシリアライゼーションコードをシングルトンクラスに追加することで、同じことを自分で行うことができます。そのコードは、シングルトンの状態をまったく記録しないか、シングルトンがシリアル化解除されたときに破棄する必要があります。いずれにせよ、@ ColinDの答えで説明されているように、ロジックをreadResolve()
メソッドに入れます。
さて、シングルトンをシリアル化する理由は、シングルトンの状態を保持したいからだと思います。残念ながら、それは概念的な問題を提示します。アプリケーションがイベントの通常の過程でシングルトンをインスタンス化し、その後、シングルトンの以前のインスタンスのコピーを含むオブジェクトグラフをデシリアライズするとします。何ができますか?
Enumを使用したソリューションは、Spring、EJB、Guice、またはその他のDIフレームワークによって管理されるシングルトンでは機能しません。 enumがシリアル化アルゴリズムによって特別に処理されるため、enumでのみ機能します。
まず、シングルトンはシリアル化を必要としません。なぜなら、それを逆シリアル化し、シングルトンを逆シリアル化した場合!= YourSingleton.getInstance()、つまりシングルトンのインスタンスが2つあることを意味します。予測できないバグにつながる可能性があるシングルトンではありません。
ただし、シングルトンへの参照を含む非シングルトンをシリアル化する必要がある場合があります。解決策は簡単です。
class NonSingleton implements Serializable {
private transient YourSingleton singleton = YourSingleton.getInstance();
...
}
春の場合:
@Configurable
class NonSingleton implements Serializable {
@Autowired
private transient YourSingleton singleton;
...
}
以下は、Singleton
インターフェイスを実装するSerializable
クラスです。 readResolve()
メソッドも含まれていることをマークします。
_import Java.io.Serializable;
public class Singleton implements Serializable {
private static Singleton singleton = new Singleton( );
public int i = 1;
private Singleton() { }
public static Singleton getInstance( ) {
return singleton;
}
public Object readResolve() {
return getInstance( );
}
public static void main(String[] args) {
Singleton s1 = getInstance();
System.out.println(s1.hashCode());
Singleton s2 = getInstance();
System.out.println(s2.hashCode());
}
}
_
以下は、最初に上記のクラスをシリアル化してから逆シリアル化するクラスです。ここでは、逆シリアル化が2回行われますが、readResolve()メソッドにより、どちらの場合も1つのインスタンスのみが作成されます。
_public class SingletonSerializableDemo {
static Singleton sing = Singleton.getInstance();
static Singleton s1 = null;
static Singleton s2 = null;
public static void main(String[] args) {
try {
FileOutputStream fileOut =
new FileOutputStream("E:/singleton.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(sing);
out.close();
fileOut.close();
System.out.println("Serialized data is saved");
FileInputStream fileIn1 = new FileInputStream("E:/singleton.ser");
FileInputStream fileIn2 = new FileInputStream("E:/singleton.ser");
ObjectInputStream in1 = new ObjectInputStream(fileIn1);
ObjectInputStream in2 = new ObjectInputStream(fileIn2);
s1 = (Singleton) in1.readObject();
s2 = (Singleton) in2.readObject();
System.out.println(s1.hashCode() + " "+ s1.i);
s1.i = 10;
System.out.println(s2.hashCode() + " "+ s2.i);
in1.close();
in2.close();
fileIn1.close();
fileIn2.close();
}catch(Exception i) {
i.printStackTrace();
}
}
}
_
出力は次のようになります。
シリアル化されたデータが保存されます
21061094 1
21061094 10
結論:シングルトンクラスは、readResolve()
メソッドをシングルトンクラスに保持することによってもシリアル化できます。
これはおなじみのソリューションかもしれませんが、参考のためだけに。
public class ConnectionFactory implements Serializable {
//Static variable for holding singleton reference object
private static ConnectionFactory INSTANCE;
/**
* Private constructor
*/
private ConnectionFactory() {
}
/**
* Static method for fetching the instance
*
* @return
*/
public static ConnectionFactory getIntance() {
//Check whether instance is null or not
if (INSTANCE == null) {
//Locking the class object
synchronized (ConnectionFactory.class) {
//Doing double check for the instance
//This is required in case first time two threads simultaneously invoke
//getInstance().So when another thread get the lock,it should not create the
//object again as its already created by the previous thread.
if (INSTANCE == null) {
INSTANCE = new ConnectionFactory();
}
}
}
return INSTANCE;
}
/**
* Special hook provided by serialization where developer can control what object needs to sent.
* However this method is invoked on the new object instance created by de serialization process.
*
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
コードをテストする
public class SerializationTest {
public static void main(String[] args) {
ConnectionFactory INSTANCE = ConnectionFactory.getIntance();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connectFactory.ser"));
oos.writeObject(INSTANCE);
oos.close();
ObjectInputStream osi = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
ConnectionFactory factory1 = (ConnectionFactory) osi.readObject();
osi.close();
ObjectInputStream osi2 = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
ConnectionFactory factory2 = (ConnectionFactory) osi2.readObject();
osi2.close();
System.out.println("Instance reference check->" + factory1.getIntance());
System.out.println("Instance reference check->" + factory2.getIntance());
System.out.println("===================================================");
System.out.println("Object reference check->" + factory1);
System.out.println("Object reference check->" + factory2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
出力
Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
===================================================
Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
次のシングルトンクラスがあるとします。
public class ConnectionFactory implements Serializable {
private static ConnectionFactory INSTANCE;
private ConnectionFactory() { }
public static ConnectionFactory getInstance() {
if (INSTANCE == null) {
synchronized(ConnectionFactory.class) {
if(INSTANCE == null)
INSTANCE = new ConnectionFactory();
}
}
return INSTANCE;
}
}
これで、オブジェクトのシリアライズとデシリアライズのための以下のようなメインクラスができました。
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ConnectionFactory INSTANCE=ConnectionFactory.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connFactory.ser"));
oos.writeObject(INSTANCE);
oos.close();
// Here I am recreating the instance by reading the serialized object data store
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("connFactory.ser"));
ConnectionFactory factory1 = (ConnectionFactory) ois.readObject();
ois.close();
// I am recreating the instance AGAIN by reading the serialized object data store
ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("connFactory.ser"));
ConnectionFactory factory2 = (ConnectionFactory) ois2.readObject();
ois2.close();
// Let's see how we have broken the singleton behavior
System.out.println("Instance reference check->" +factory1.getInstance());
System.out.println("Instance reference check->" +factory2.getInstance());
System.out.println("=========================================================");
System.out.println("Object reference check->" + factory1);
System.out.println("Object reference check->" + factory2);
}
したがって、上記のコードを実行すると、次の動作が得られます: "INSTANCEの2つのオブジェクトと1つの静的参照を作成しました。つまり、シングルトンオブジェクトのシリアル化形式を複数回読み取ると、複数のオブジェクトを作成します。これは、シングルトンオブジェクトが行うことを想定したものではありません。したがって、iを回避できますか?、はい、できます。 "
シングルトンクラスの複数のインスタンスを回避するには、シリアル化によって提供される次のメソッドを使用します。
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
これにより、シングルトンクラスの複数のインスタンスが作成されなくなります。
シングルトンはシリアル化できると思います。その方法を示すコードを次に示します。
import Java.io.Serializable;
public class MySingleton implements Serializable {
private MySingleton(String name) {
this.name = name;
}
private static MySingleton mySingleton;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static MySingleton getInstance(String name) {
if(mySingleton == null) {
System.out.println("in if...");
mySingleton = new MySingleton(name);
}
return mySingleton;
}
}
そして、上記のシングルトンクラスのインスタンスを取得し、シリアル化および非シリアル化する「メイン」メソッドがあります。
public static void main (String[] args) {
MySingleton m = MySingleton.getInstance("Akshay");
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://temp.ser"));
oos.writeObject(m);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://temp.ser"));
MySingleton m2 = (MySingleton) ois.readObject();
System.out.println(m2.getName());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
出力は次のとおりです。
で...
アクシェイ
ありがとう。
シングルトンクラスを破壊するための答えと、readResolve()methoodを使用してクラスが異なるオブジェクトを作成しないようにする方法を示します。
import Java.io.Serializable;
パブリッククラスシングルトンはSerializable {
private static final long serialVersionUID = 1L;
private Singleton() {
}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
private Object readResolve() {
Singleton instance = getInstance();
return instance;
}
}
パブリッククラスBreakSIngletonUsingSerialization {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Singleton demo1 =Singleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("C:/Eclipse/serial.ser"));
out.writeObject(demo1);
Singleton demo2 =null;
ObjectInput in = new ObjectInputStream(new FileInputStream("C:/Eclipse/serial.ser"));
demo2 = (Singleton)in.readObject();
System.out.println("Hascode demo1 : " +demo1);
System.out.println("Hascode demo2 : " +demo2);
}
}