`

反射、枚举与单例

阅读更多
通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:
public final class Singleton {

	private static final Singleton instance=new Singleton();
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		return instance;
	}
}

测试案例如下:
Singleton singleton1=Singleton.getInstance();
		Singleton singleton2=Singleton.getInstance();
		
		Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		Singleton singleton3=constructor.newInstance();
		
		System.out.println(singleton1);
		System.out.println(singleton2);
		System.out.println(singleton3);
		System.out.println(singleton1==singleton2);
		System.out.println(singleton1==singleton3);

其中singleton1、singleton2都是通过我们所实现的单例模式来获取的对象,他们应该是同一个对象,singleton3则是通过反射获取无参构造器,constructor.setAccessible(true)来获取访问权限,最后通过无参构造器来创建一个对象singleton3,singleton3和上述两者应该不是同一个对象,测试结果如下:
com.lg.design.singleton.hungry.Singleton@15e3d24a
com.lg.design.singleton.hungry.Singleton@15e3d24a
com.lg.design.singleton.hungry.Singleton@20030380
true
false

所以说通常我们所使用的单例模式,我们都可以使用反射使它不再单例。然而单例使用枚举的话,却可以避免被反射。
单例如下:

public enum Singleton {
	
	instance;
	private Singleton(){}
	
}

反射如下:
Singleton singleton1=Singleton.instance;
		Singleton singleton2=Singleton.instance;
		
		Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		Singleton singleton3=constructor.newInstance();
		
		System.out.println(singleton1);
		System.out.println(singleton2);
		System.out.println(singleton3);
		System.out.println(singleton1==singleton2);
		System.out.println(singleton1==singleton3);

然后就报错:
Exception in thread "main" java.lang.NoSuchMethodException: com.lg.design.singleton.enumsingleton.Singleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:2849)
	at java.lang.Class.getDeclaredConstructor(Class.java:2053)
	at com.lg.design.singleton.enumsingleton.Test.main(Test.java:14)

没有这个无参构造器,通过调试Singleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,然后我们就可以明白了,这里的参数其实就是枚举的名字和所在枚举中位置,即枚举的name和ordinal两个属性,枚举的源码如下:
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;
    }

    public String toString() {
        return name;
    }
 //略
}

枚举Enum是一个抽象类,一个类一旦声明为枚举,其实就是继承了Enum,所以会有(String.class,int.class)的构造器(就是父类Enum的构造器),具体的原理可以根据生成的字节码反编译后得知,可以参考这篇文章http://pf-miles.iteye.com/blog/187155#bc2340028
既然是可以获取到父类Enum的构造器,那我们就使用该构造器看能不能创建出对象:

Singleton singleton1=Singleton.instance;
		Singleton singleton2=Singleton.instance;
		
		Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor(String.class,int.class);
		//Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		Singleton singleton3=constructor.newInstance("otherInstance",9);
		//Singleton singleton3=constructor.newInstance();
		
		System.out.println(singleton1);
		System.out.println(singleton2);
		System.out.println(singleton3);
		System.out.println(singleton1==singleton2);
		System.out.println(singleton1==singleton3);

然后也报错:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:521)
	at com.lg.design.singleton.enumsingleton.Test.main(Test.java:16)


之前的错是说没有构造器,这次我们能够拿到构造器了,只是在使用构造器执行newInstance("otherInstance",9)方法时抛出异常,说不能够反射枚举,具体源码如下:
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);
            }
        }
//我们关注的重点,如果类含有ENUM修饰,调用该方法时直接报错
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        return (T) ca.newInstance(initargs);
    }

也就是说反射在通过newInstance创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。
也就是说使用枚举可以避免被反射,从而可以达到单例的效果。

若想转载请注明出处:   http://lgbolgger.iteye.com/blog/2159940
作者:iteye的乒乓狂魔
分享到:
评论

相关推荐

    JAVA 枚举单例模式及源码分析的实例详解

    JAVA 枚举单例模式是一种特殊的单例模式实现方式,它使用枚举类型来保证线程安全、防止序列化问题和反射攻击。下面我们将详细解释这个模式的实现原理和源码分析。 线程安全 在 Java 中,枚举类型的实例是在类加载...

    com_枚举方式实现单例模式_代码详解.rar

    以下是一个简单的枚举单例模式的示例: ```java public enum Singleton { INSTANCE; public void someService() { // 实现服务逻辑 } } ``` 在这个例子中,Singleton是一个枚举类,它有一个名为INSTANCE的...

    xml、单例模式、反射、枚举

    xml、单例模式、反射、枚举.

    实验12 单例模式与枚举.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    JAVA反射机制与单例模式

    5. 枚举单例:最安全且避免了序列化问题,但可能不适合需要扩展的情况。 编写单例模式需要注意以下问题: 1. 避免多线程环境下的并发问题,确保实例的唯一性。 2. 考虑序列化可能导致的多个实例,需要在类中添加`...

    单例模式(饿汉模式、懒汉模式、DCL单例模式、枚举)

    本文将详细讨论四种常见的单例实现方式:饿汉模式、懒汉模式、双重检查锁定(DCL)单例模式以及枚举单例。 1. **饿汉模式**: 饿汉模式是在类加载时就完成了实例化,避免了线程同步问题。这种方式简单且安全,但...

    java 枚举(enum) 详解(学习资料)

    《Effective Java》和《Java 与模式》等书籍推荐使用枚举实现单例,因为它们提供了一种天然的防篡改机制,保证了在任何情况下都只有一个实例。 5. **枚举的优势**: - **安全性**:枚举对象是不可变的,防止了意外...

    Java 单例模式 工具类

    6. 枚举型单例: 最安全的实现方式,天然线程安全,防止反射攻击。 ```java public enum Singleton { INSTANCE; } ``` 现在,我们将创建一个工具类`SingletonFactory`来封装这些单例模式的实现。`SingletonFactory...

    单例模式各种实现方式

    枚举的单例在Java中被认为是最好的实现方式,因为它不仅线程安全,而且防止了序列化和反射攻击。 每种实现方式都有其特点和适用场景,开发者应根据实际需求选择合适的单例实现。在实际项目中,还要考虑性能、线程...

    单例模式的反射漏洞和反序列化漏洞代码实例

    其中,枚举式单例模式是最安全的实现方式,因为它可以避免反射漏洞和反序列化漏洞。其他四种实现方式都存在反射漏洞和反序列化漏洞,需要采取相应的防护措施来避免这些漏洞。 在实际开发中,需要根据实际情况选择...

    java单例模式详解Java系列2021.pdf

    枚举单例写法简洁,能够避免反射破坏单例的问题,并且它还能够防止反序列化重新创建新的实例。使用枚举实现单例模式时,Java虚拟机会保证枚举类型中的字段只被实例化一次。 在Java的单例模式实现中,内部类也是一个...

    java 单例模式

    4.枚举单例是 Java 中推荐的实现方式,它天然线程安全,无需额外的同步: ```java public enum SingletonClass { INSTANCE; public void singletonMethod() { // ... } } ``` 枚举的单例在防止反射攻击方面也...

    Java的单例设计模式

    如果希望防止单例被恶意反射创建,枚举单例是最佳选择。 在使用单例模式时,需要注意以下几点: 1. 避免过早初始化:懒汉式可以延迟初始化,减少不必要的资源占用。 2. 确保线程安全:在多线程环境下,需要确保单例...

    Java实现多种单例模式

    在Java编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中尤其...而在枚举单例中,由于其简洁性和安全性,通常被视为最佳实践。

    设计模式——单例模式(懒汉模式)

    枚举的单例模式简单易懂,且天然线程安全,不会受到反射和序列化攻击的影响,是推荐的单例实现方式之一。 总的来说,单例模式是一种常见的设计模式,懒汉式单例模式则是其中一种实现策略,它的主要特点是延迟加载和...

    单例模式与双重检测

    除了上述的单例模式实现,Java中还提供了`Enum`方式来创建单例,这种方式不仅简单且线程安全,同时也避免了反射和序列化攻击。例如: ```java public enum Singleton { INSTANCE; } ``` 通过枚举方式实现单例,...

    单例模式.ppt

    5. **枚举单例**: 将单例定义为一个枚举类型,这样不仅能保证线程安全,还能防止反射攻击。枚举是JVM的固有特性,因此它的创建是线程安全的,并且不允许实例化多个枚举实例。 每种实现方式都有其优缺点,选择哪种...

    设计模式之单例模式(结合工厂模式)

    静态内部类单例利用Java类加载机制保证了线程安全,而枚举单例则是Java中实现单例的最佳方式,因为它天然支持序列化且防止反射攻击。 在代码实现上,我们可以创建一个名为SingletonFactory的工厂类,其中包含一个...

    单例模式(Singleton)的6种实现

    枚举的单例在JVM层面就保证了唯一性,无需额外同步操作。 总结起来,单例模式的实现方式各有优缺点,需要根据实际应用场景选择合适的方式。需要注意的是,过度使用单例可能导致设计上的问题,如违反单一职责原则,...

    浅谈Spring单例Bean与单例模式的区别

    在Java中,单例模式可以通过多种方式实现,如懒汉模式、饿汉模式、双重检查锁模式、枚举模式等。下面是一个简单的懒汉模式实现单例模式的示例代码: ```java public class Singleton { private static Singleton ...

Global site tag (gtag.js) - Google Analytics