所有分类
  • 所有分类
  • 未分类

单例模式Java实战-枚举类防止破坏单例

简介

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属性拿到原有的对象,因此保证了枚举类型实现单例模式的序列化安全。 ​

2

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录