aka*_*lou 12 java singleton enums design-patterns
您知道,自Java 5发布以来,在Java中编写Singleton模式的推荐方法是使用枚举.
public enum Singleton {
INSTANCE;
}
Run Code Online (Sandbox Code Playgroud)
但是,我不喜欢这个 - 是强制客户端使用Singleton.INSTANCE来访问单例实例.也许,更好的方法是在普通类中隐藏Singleton,并提供更好的单例设施访问:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
所以,客户现在可以简单地写:
ApplicationSingleton.getResourceAsString("application.name")
Run Code Online (Sandbox Code Playgroud)
例如.哪个好多了:
Singleton.INSTANCE.getResourceAsString("application.name")
Run Code Online (Sandbox Code Playgroud)
所以,问题是:这是正确的方法吗?此代码是否有任何问题(线程安全?)?它具有"enum singleton"模式的所有优点吗?这似乎从两个世界都变得更好.你怎么看?有没有更好的方法来实现这一目标?谢谢.
编辑
@all
首先,有效Java,第2版:维基百科:Java Enum Singleton中提到了Singleton模式的枚举用法.我完全同意我们应该尽可能地减少Singleton的使用,但我们不能完全放弃它们.
在我提供另一个示例之前,让我说,使用ResourceBundle的第一个示例只是一个案例,示例本身(和类名称)不是来自实际应用程序.但是,需要说的是,我不了解ResourceBundle缓存管理,感谢那条信息)
下面,Singleton模式有两种不同的方法,第一种是使用Enum的新方法,第二种是我们大多数人之前使用的标准方法.我试图表明它们之间的显着差异.
使用Enum的Singleton:
ApplicationSingleton类是:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
注册表类是:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
和测试类:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
这是输出:
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
Run Code Online (Sandbox Code Playgroud)
应该提到什么:
因此,总结一下:Enum方法工作正常,可防止通过反射攻击创建重复的Singleton,并在序列化后返回相同的实例.
Singleton使用标准方法:
ApplicationSingleton类是:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
注册表类是(请注意,注册表和数据类显式应该实现Serializable,以便序列化工作):
//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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
和ApplicationSingletionTest类(大致相同):
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());
}
}
Run Code Online (Sandbox Code Playgroud)
这是输出:
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
Run Code Online (Sandbox Code Playgroud)
应该提到什么:
因此,总结一下:标准方法对于反射攻击来说很弱,并且在序列化后返回不同的实例,但是对于相同的数据则是.
因此,似乎Enum方法更加稳固可靠.现在是在Java中使用Singleton模式的推荐方法吗?你怎么看?
有趣的事实要解释:为什么enum中的对象可以使用其拥有的类进行序列化并不实现Serializable?是功能还是bug?
jpr*_*ete 16
我不知道枚举是这些天构建单身人士的Java方式.但是如果你打算这样做,你也可以直接使用枚举.我认为没有任何理由将单例封装在一堆静态成员方法之后; 一旦你完成了这个,你也可以编写一个静态类,其中包含私有静态成员.