You know, that since Java 5 is released the recommended way to write Singleton pattern in Java is using enum.
public enum Singleton {
INSTANCE;
}
But, what I don't like in this - is to force the client to use Singleton.INSTANCE in order to have access to the singleton instance. Maybe, the better way to hide Singleton inside the ordinary class, and provide more better access to singleton facilities:
public class ApplicationSingleton {
private static enum Singleton {
INSTANCE;
private ResourceBundle bundle;
private Singleton() {
System.out.println("Singleton instance is created: " +
System.currentTimeMillis());
bundle = ResourceBundle.getBundle("application");
}
private ResourceBundle getResourceBundle() {
return bundle;
}
private String getResourceAsString(String name) {
return bundle.getString(name);
}
};
private ApplicationSingleton() {}
public static ResourceBundle getResourceBundle() {
return Singleton.INSTANCE.getResourceBundle();
}
public static String getResourceAsString(String name) {
return Singleton.INSTANCE.getResourceAsString(name);
}
}
So, the client now can simply write:
ApplicationSingleton.getResourceAsString("application.name")
for example. Which is much better then:
Singleton.INSTANCE.getResourceAsString("application.name")
So, the question is: Is it the right way to do it? Does this code have any issues (thread-safety?)? Does it have all the advantages the "enum singleton" pattern has? It seems that it takes the better from the both world. What do you think? Is any better way to achieve this? Thanks.
EDIT
@all
First of all enum usage for Singleton pattern was mentioned in the Effective Java, 2nd Edition: wikipedia:Java Enum Singleton. I totally agree that we should minimize Singleton usage as much as possible, but we can't totally go away from them.
Before I provide another example, let me say, that the first example with ResourceBundle is just a case, the example itself (and the classes names) are not from the real application. But, need to say, that I didn't know about ResourceBundle cache management, thanks for that piece of information )
Below, there are 2 different approaches for Singleton pattern, the first is the new approach with Enum, and the second is the standard approach most of us used before. And I try to show significant differences between them.
Singleton using Enum:
ApplicationSingleton class is:
public class ApplicationSingleton implements Serializable {
private static enum Singleton {
INSTANCE;
private Registry registry;
private Singleton() {
long currentTime = System.currentTimeMillis();
System.out.println("Singleton instance is created: " +
currentTime);
registry = new Registry(currentTime);
}
private Registry getRegistry() {
return registry;
}
private long getInitializedTime() {
return registry.getInitializedTime();
}
private List<Registry.Data> getData() {
return registry.getData();
}
};
private ApplicationSingleton() {}
public static Registry getRegistry() {
return Singleton.INSTANCE.getRegistry();
}
public static long getInitializedTime() {
return Singleton.INSTANCE.getInitializedTime();
}
public static List<Registry.Data> getData() {
return Singleton.INSTANCE.getData();
}
}
Registry class is:
public class Registry {
private List<Data> data = new ArrayList<Data>();
private long initializedTime;
public Registry(long initializedTime) {
this.initializedTime = initializedTime;
data.add(new Data("hello"));
data.add(new Data("world"));
}
public long getInitializedTime() {
return initializedTime;
}
public List<Data> getData() {
return data;
}
public class Data {
private String name;
public Data(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
And test class:
public class ApplicationSingletonTest {
public static void main(String[] args) throws Exception {
String rAddress1 =
ApplicationSingleton.getRegistry().toString();
Constructor<ApplicationSingleton> c =
ApplicationSingleton.class.getDeclaredConstructor();
c.setAccessible(true);
ApplicationSingleton applSingleton1 = c.newInstance();
String rAddress2 = applSingleton1.getRegistry().toString();
ApplicationSingleton applSingleton2 = c.newInstance();
String rAddress3 = applSingleton2.getRegistry().toString();
// serialization
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(applSingleton1);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();
String rAddress4 = applSingleton3.getRegistry().toString();
List<Registry.Data> data = ApplicationSingleton.getData();
List<Registry.Data> data1 = applSingleton1.getData();
List<Registry.Data> data2 = applSingleton2.getData();
List<Registry.Data> data3 = applSingleton3.getData();
System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
ApplicationSingleton.getInitializedTime(),
applSingleton1.getInitializedTime(),
applSingleton2.getInitializedTime(),
applSingleton3.getInitializedTime());
}
}
And here is the output:
Singleton instance is created: 1304067070250
applSingleton1=ApplicationSingleton@18a7efd, applSingleton2=ApplicationSingleton@e3b895, applSingleton3=ApplicationSingleton@6b7920
rAddr1=Registry@1e5e2c3, rAddr2=Registry@1e5e2c3, rAddr3=Registry@1e5e2c3, rAddr4=Registry@1e5e2c3
dAddr1=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr2=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr3=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr4=[Registry$Data@1dd46f7, Registry$Data@5e3974]
time0=1304067070250, time1=1304067070250, time2=1304067070250, time3=1304067070250
What should to mention:
So, to summarize: Enum approach works fine and prevent duplicate Singleton creation through reflection attack, and return the same instance after being serialized.
Singleton using standard approach:
ApplicationSingleton class is:
public class ApplicationSingleton implements Serializable {
private static ApplicationSingleton INSTANCE;
private Registry registry;
private ApplicationSingleton() {
try {
Thread.sleep(10);
} catch (InterruptedException ex) {}
long currentTime = System.currentTimeMillis();
System.out.println("Singleton instance is created: " +
currentTime);
registry = new Registry(currentTime);
}
public static ApplicationSingleton getInstance() {
if (INSTANCE == null) {
return newInstance();
}
return INSTANCE;
}
private synchronized static ApplicationSingleton newInstance() {
if (INSTANCE != null) {
return INSTANCE;
}
ApplicationSingleton instance = new ApplicationSingleton();
INSTANCE = instance;
return INSTANCE;
}
public Registry getRegistry() {
return registry;
}
public long getInitializedTime() {
return registry.getInitializedTime();
}
public List<Registry.Data> getData() {
return registry.getData();
}
}
Registry class is (note that Registry and Data classes explicitly should implement Serializable in order serialization to work):
//now Registry should be Serializable in order serialization to work!!!
public class Registry implements Serializable {
private List<Data> data = new ArrayList<Data>();
private long initializedTime;
public Registry(long initializedTime) {
this.initializedTime = initializedTime;
data.add(new Data("hello"));
data.add(new Data("world"));
}
public long getInitializedTime() {
return initializedTime;
}
public List<Data> getData() {
return data;
}
// now Data should be Serializable in order serialization to work!!!
public class Data implements Serializable {
private String name;
public Data(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
And ApplicationSingletionTest class is (mostly the same):
public class ApplicationSingletonTest {
public static void main(String[] args) throws Exception {
String rAddress1 =
ApplicationSingleton.getInstance().getRegistry().toString();
Constructor<ApplicationSingleton> c =
ApplicationSingleton.class.getDeclaredConstructor();
c.setAccessible(true);
ApplicationSingleton applSingleton1 = c.newInstance();
String rAddress2 = applSingleton1.getRegistry().toString();
ApplicationSingleton applSingleton2 = c.newInstance();
String rAddress3 = applSingleton2.getRegistry().toString();
// serialization
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(applSingleton1);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();
String rAddress4 = applSingleton3.getRegistry().toString();
List<Registry.Data> data = ApplicationSingleton.getInstance().getData();
List<Registry.Data> data1 = applSingleton1.getData();
List<Registry.Data> data2 = applSingleton2.getData();
List<Registry.Data> data3 = applSingleton3.getData();
System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
ApplicationSingleton.getInstance().getInitializedTime(),
applSingleton1.getInitializedTime(),
applSingleton2.getInitializedTime(),
applSingleton3.getInitializedTime());
}
}
And here is the output:
Singleton instance is created: 1304068111203
Singleton instance is created: 1304068111218
Singleton instance is created: 1304068111234
applSingleton1=ApplicationSingleton@16cd7d5, applSingleton2=ApplicationSingleton@15b9e68, applSingleton3=ApplicationSingleton@1fcf0ce
rAddr1=Registry@f72617, rAddr2=Registry@4f1d0d, rAddr3=Registry@1fc4bec, rAddr4=Registry@1174b07
dAddr1=[Registry$Data@1256ea2, Registry$Data@82701e], dAddr2=[Registry$Data@1f934ad, Registry$Data@fd54d6], dAddr3=[Registry$Data@18ee9d6, Registry$Data@19a0c7c], dAddr4=[Registry$Data@a9ae05, Registry$Data@1dff3a2]
time0=1304068111203, time1=1304068111218, time2=1304068111234, time3=1304068111218
What should to mention:
So, to summarize: Standard approach is weak for reflection attack, and return different instance after being serialized, but yes with the same data.
So, it seems that Enum approach is more solid and robust. And is it the recommended way for using Singleton pattern in Java nowadays? What do you think?
Interesting fact to explain: why objects inside enum can be serialized with its owning class does not implement Serializable? Is it feature or bug?
clone method is protected and you cannot subclass a singleton class.
Prevent Singleton Pattern From Cloning To overcome the above issue, we need to implement/override the clone() method and throw an exception CloneNotSupportedException from the clone method. If anyone tries to create a clone object of Singleton , it will throw an exception, as shown in the below code.
The most popular approach is to implement a Singleton by creating a regular class and making sure it has: A private constructor. A static field containing its only instance. A static factory method for obtaining the instance.
Cloning: Cloning is a concept to create duplicate objects. Using clone we can create copy of object. Suppose, we create clone of a singleton object, then it will create a copy that is there are two instances of a singleton class, hence the class is no more singleton.
I was not aware that enums were the Java way to build singletons these days. But if you're going to do it that way, you may as well just use the enum directly. I don't see any good reason to encapsulate the singleton behind a bunch of static member methods; once you've done that, you may as well have written a static class with private static members to begin with.
The "better" singleton pattern is not to use one.
The approach that you describe, like all approaches that create the singleton via static initialization, is extremely hard to debug.
Instead, use dependency injection (with or without a framework such as Spring).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With