Вы знаете, что, поскольку выпущена Java 5, рекомендуется использовать шаблон Singleton в Java, используя enum.
public enum Singleton {
INSTANCE;
}
Но мне не нравится в этом - заставить клиента использовать 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);
}
}
Итак, теперь клиент может просто написать:
ApplicationSingleton.getResourceAsString("application.name")
например. Что намного лучше:
Singleton.INSTANCE.getResourceAsString("application.name")
Итак, вопрос: правильно ли это сделать? Имеет ли этот код какие-либо проблемы (безопасность потока?)? Имеет ли он все преимущества, присущие шаблону "enum singleton"? Похоже, что он берет лучшее от мира. Как вы думаете? Есть ли лучший способ достичь этого? Благодарю.
ИЗМЕНИТЬ
@all
Прежде всего использование перечислений для шаблона Singleton было упомянуто в "Эффективной Java", 2-е издание: wikipedia: Java Enum Singleton. Я полностью согласен с тем, что мы должны максимально минимизировать использование Singleton, но мы не можем полностью уйти от них.
Прежде чем я приведу другой пример, позвольте мне сказать, что первый пример с ResourceBundle - это всего лишь случай, сам пример (и имена классов) не от реального приложения. Но, нужно сказать, что я не знал об управлении кешем ResourceBundle, спасибо за эту информацию)
Ниже приведено два разных подхода к шаблону Singleton, первый - новый подход с Enum, а второй - стандартный подход, который большинство из нас использовало ранее. И я пытаюсь показать существенные различия между ними.
Синглтон с использованием Enum:
Класс 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();
}
}
Класс реестра:
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;
}
}
}
И тестовый класс:
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());
}
}
И вот вывод:
Singleton instance is created: 1304067070250
[email protected], [email protected], [email protected]
[email protected], [email protected], [email protected], [email protected]
dAddr1=[[email protected], [email protected]], dAddr2=[[email protected], [email protected]], dAddr3=[[email protected], [email protected]], dAddr4=[[email protected], [email protected]]
time0=1304067070250, time1=1304067070250, time2=1304067070250, time3=1304067070250
Что нужно сказать:
- Экземпляр Singleton был создан только один раз
- Да, существует несколько разных экземпляров ApplicationSingletion, но все они содержат один и тот же экземпляр Singleton
- Внутренние данные реестра одинаковы для всех разных экземпляров ApplicationSingleton
Итак, суммируем: подход Enum работает отлично и предотвращает дублирование создания 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();
}
}
Класс реестра (обратите внимание, что классы реестра и данных явно должны реализовывать 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;
}
}
}
И класс 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());
}
}
И вот вывод:
Singleton instance is created: 1304068111203
Singleton instance is created: 1304068111218
Singleton instance is created: 1304068111234
[email protected], [email protected], [email protected]
[email protected], [email protected], [email protected], [email protected]
dAddr1=[[email protected], [email protected]], dAddr2=[[email protected], [email protected]], dAddr3=[[email protected], [email protected]], dAddr4=[[email protected], [email protected]]
time0=1304068111203, time1=1304068111218, time2=1304068111234, time3=1304068111218
Что нужно сказать:
- Экземпляр Singleton был создан несколькими! раз
- Все объекты реестра - это разные объекты со своими собственными данными.
Итак, подведем итог: стандартный подход является слабым для атаки отражения и возвращает другой экземпляр после сериализации, но да с теми же данными.
Итак, кажется, что подход Enum более прочный и надежный. И рекомендуется ли использовать шаблон Singleton в Java в наши дни? Как вы думаете?
Интересный факт для объяснения: почему объекты внутри enum могут быть сериализованы с его собственным классом, не реализует Serializable? Это функция или ошибка?