抵御反射创建单例模式的对象

本文探讨了在Java中实现Singleton模式的不同方法,包括使用静态字段、静态方法及枚举,并介绍了如何防止通过反射创建多个实例,同时讨论了序列化时的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    《Effective Java》一书中第三条经验指出:“用私有构造器或者枚举类型强化Singleton”,其中先提出了两种单例模式。

1.暴露静态final字段

public class Elvis {  
  
    public static final Elvis INSTANCE = new Elvis();  
      
    private Elvis(){;;;}  
      
    public void leaveTheBuilding() {;;;}  
  
}  

 2.暴露静态方法

public class Elvis {  
  
    private static final Elvis INSTANCE = new Elvis();  
      
    private Elvis(){;;;}  
      
    public static Elvis getInstance() { return INSTANCE;}  
    public void leaveTheBuilding() {;;;}  
  
}

但是如果借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器,都可以创建新的对象绕过了Java的私有语义,可以通过增加一个计数器来统计创建的对象个数,通过抛出异常的形式来阻止创建新的对象。

package hello;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Singleton
 *
 * @author crystalChen
 * @date 16/6/4 11:38
 */
public class Singleton {

    private static int cntInstance = 0;  //防止第二次new对象
    private static volatile Singleton instance = null;

    private Singleton() throws Exception{
        if (++cntInstance > 1)
            throw new Exception("try to new more than one singleton.");
        System.out.println("new a singleton.");

    }

    public static Singleton getInstance() throws Exception{
        if (null == instance) { //为了简洁度,不考虑线程安全
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        try {
            Singleton.getInstance();

            Class singletonClass = Class.forName("hello.Singleton");
//          Constructor[] cts = singletonClass.getConstructors(); 这种方式找不到私有构造器
            Constructor[] cts = singletonClass.getDeclaredConstructors();
            System.out.println("size: " + cts.length);
            cts[0].setAccessible(true);  //访问权限打开setAccessible(true),就可以访问私有构造函数
            Singleton singleton = (Singleton)cts[0].newInstance();

        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
抛出异常:
/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63750,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/tools.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Users/happyelements/Documents/javaProject/javaWeb/out/production/javaWeb:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.annotation.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.ejb.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.transaction.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.persistence.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.resource.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.jms.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jsp.jstl.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jsp.jar:/Applications/IntelliJ IDEA 14.app/Contents/lib/idea_rt.jar" hello.Singleton
Connected to the target VM, address: '127.0.0.1:63750', transport: 'socket'
new a singleton.
size: 1
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
	at hello.Singleton.main(Singleton.java:41)
Caused by: java.lang.Exception: try to new more than one singleton.
	at hello.Singleton.<init>(Singleton.java:20)
	... 5 more
Disconnected from the target VM, address: '127.0.0.1:63750', transport: 'socket'

Process finished with exit code 0

但是如果想序列化单例的话,除了实现Serializable接口,注意:

1. 声明所有实例域都是transient的;

2.提供一个readResolve方法,返回单例对象。否则,反序列化的时候,会构建一个新的实例,违反了单例的原则。

private Object readResolve() {  
    return <span style="font-family: Helvetica, Tahoma, Arial, sans-serif;">instance</span>;  
} 


JDK1.5及以后,增加了实现Singleton的第三种方法。只需编写一个包含单个元素的枚举类型。

<span style="font-family:Helvetica, Tahoma, Arial, sans-serif;">public enum Elvis {  
  
    INSTANCE;   //只有一个元素  
      
    public void leaveTheBuilding() {;;;}   
}  </span>

这种方法更加简洁,无偿地提供序列化机制,绝对安全。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。


 

    



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值