简介
Java可以用枚举类写法来实现单例模式,它可以防止反射和序列化破坏单例。
枚举类单例模式的写法
public enum Singleton { INSTANCE; }
防止反射破坏单例
例1:无参构造函数
实例
package org.example.a; import java.lang.reflect.Constructor; public class Demo { public static void main(String[] args) { Class<?> singletonClass = Singleton.class; try { Constructor c = singletonClass.getDeclaredConstructor(null); c.setAccessible(true); Object singleton1 = c.newInstance(); Object singleton2 = c.newInstance(); System.out.println(singleton1 == singleton2); } catch (Throwable e) { e.printStackTrace(); } } }
执行结果
java.lang.NoSuchMethodException: org.example.a.Singleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at org.example.a.Demo.main(Demo.java:9)
原理
SingletonClass.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,因为一旦一个类声明为枚举,实际上就是继承了Enum,来看看Enum类源码:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; } protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } //余下省略 }
例2:使用(String.class,int.class)构造器
实例
package org.example.a; import java.lang.reflect.Constructor; public class Demo { public static void main(String[] args) { Class<?> singletonClass = Singleton.class; try { Constructor c = singletonClass.getDeclaredConstructor(String.class, int.class); c.setAccessible(true); Object singleton1 = c.newInstance(); Object singleton2 = c.newInstance(); System.out.println(singleton1 == singleton2); } catch (Throwable e) { e.printStackTrace(); } } }
执行结果
java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at org.example.a.Demo.main(Demo.java:11)
原理
追踪java.lang.reflect.Constructor#newInstance
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
重点看:
if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举是不怕反射攻击的。
防止序列化破坏单例
简介
枚举可避免被反序列化破坏单例。原因:枚举对象的序列化、反序列化有自己的一套机制:序列化时,仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。
源码分析
java.lang.Enum#valueOf
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); }
重点:T result = enumType.enumConstantDirectory().get(name); //enumConstantDirectory()返回一个map
java.lang.Class#enumConstantDirectory
Map<String, T> enumConstantDirectory() { if (enumConstantDirectory == null) { T[] universe = getEnumConstantsShared(); if (universe == null) throw new IllegalArgumentException( getName() + " is not an enum type"); Map<String, T> m = new HashMap<>(2 * universe.length); for (T constant : universe) m.put(((Enum<?>)constant).name(), constant); enumConstantDirectory = m; } return enumConstantDirectory; }
重点:m.put(((Enum<?>)constant).name(), constant);
name():枚举name;constant:枚举对象
java.lang.Class#getEnumConstantsShared
T[] getEnumConstantsShared() { if (enumConstants == null) { if (!isEnum()) return null; try { // 获取枚举的values方法 final Method values = getMethod("values"); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { values.setAccessible(true); return null; } }); @SuppressWarnings("unchecked") // 通过values方法获得枚举对象数组 T[] temporaryConstants = (T[])values.invoke(null); enumConstants = temporaryConstants; } // These can happen when users concoct enum-like classes // that don't comply with the enum spec. catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ex) { return null; } } return enumConstants; }
每个枚举对象都有一个唯一的name属性。序列化只是将name属性序列化,在反序列化的时候,通过创建一个Map(key,value),搭建起name和与之对应的对象之间的联系,然后通过索引key来获得枚举对象。
总的来说就是枚举在反序列化的过程中并没有创建新的对象,而通过name属性拿到原有的对象,因此保证了枚举类型实现单例模式的序列化安全。
请先
!