`

Effective Java(二)之用私有构造器或者枚举类型强化Singleton属性

阅读更多

书上分别说了以下三种

 

        1)将公有静态成员做成final域享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。

package com.lzw.singleton1;

import java.lang.reflect.Constructor;

/**
 * 
 * 单例实现1
 * <p>
 * 公有静态成员是个public final域
 * <p>
 * 为防止client利用反射调用私有改造函数,所以在创建第二个实例的时候抛出了异常
 * 
 * @author troyli
 * 
 */

public class Singleton1 {
	public static final Singleton1 INSTANCE = new Singleton1();

	// 私有改造函数
	private Singleton1() {
		if (INSTANCE != null) {
			throw new IllegalArgumentException("不存在第二个实例对象……");
		}
	}

	// 其他方法实现
	public void otherMethod() {
		System.out.println("call otherMethod");
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void main(String args[]) throws Exception {
		Singleton1 singleton = Singleton1.INSTANCE;
		singleton.otherMethod();

		// 利用反射调用私有构造器
		Constructor[] arrayConstructor = singleton.getClass()
				.getDeclaredConstructors();
		for (Constructor constructor : arrayConstructor) {
			// 调用setAccessible(true);
			constructor.setAccessible(true);

			// 实例化,这里一定会抛出异常
			constructor.newInstance();
		}
	}
}

 

       问题:为什么会抛出异常(不存在第二个实例对象……),这里我们分析一下代码的执行顺序。

             ①JVM 装载类的静态成员变量:public static final Singleton1 INSTANCE;

             ②执行入口方面main;

             ③获取实例对象:Singleton1 singleton = Singleton1.INSTANCE;也就会执行private Singleton1(){……}

             ④执行newInstatnce:constructor.newInstance();就会是在已经有一个对象的时候重新调用private Singleton1(){……},于是就会抛出异常。

        2)将公有静态成员做成final域和将公有成员做成静态工厂方法时,为了使Singleton类变成可序列化的,仅仅在声明上加上“implements Serializable”是不够的。

为了维护并保证Singleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。

package com.lzw.singleton2;

import java.io.Serializable;

/**
 * 
 *单例实现2
 *<p>
 * 公有的成员为静态工厂方法
 *<p>
 * 序列化时,要实现readResolve方法,防止反序列化出新的实例(这个应该怎么测试呢???)
 * 
 *@author troyli
 * 
 */

public class Singleton2 implements Serializable {
	// 私有static Instance
	private static final Singleton2 INSTANCE = new Singleton2();

	// 私有构造函数
	private Singleton2() {
	}

	// 获取单例方法
	public static Singleton2 getInstance() {
		return INSTANCE;
	}

	// 其他方法
	public void otherMethod() {
		//
	}

	// 必须提供该方法,以便重新指定反序列化得到的对象.
	private Object readResolve() {
		return INSTANCE;
	}
}

 

       这点对我是全新的东西,因此也在积累中……

       3)单元素枚举类型更加简洁、无偿的提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击时;它已经成为实现Singleton的最佳方法。

package com.lzw.singleton3;

import java.lang.reflect.Constructor;

/**
 * 
 *枚举实现单例
 *<p>
 * 目前最好的方式,避免了反射的攻击和序列化的问题
 * 
 *<pre>
 * 射调用枚举私有构造函数测试结果:
 * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Unknown Source)
	at com.lzw.singleton3.Singleton3.main(Singleton3.java:41)
 *</pre>
 * 
 *@author troyli
 * 
 */

public class Singleton3 {
	enum EnumSingleton {
		INSTANCE;

		public void otherMethod() {
		}

	}

	// 测试,是否可以反射生成枚举
	public static void main(String args[]) throws Exception {
		// 利用反射调用私有构造器
		Constructor[] arrayConstructor = EnumSingleton.INSTANCE.getClass()
				.getDeclaredConstructors();
		for (Constructor constructor : arrayConstructor) {
			// 调用setAccessible(true),设置为可以访问;
			constructor.setAccessible(true);

			// 实例化,这里一定会抛出异常
			constructor.newInstance();
		}
	}
}

 

        可以看上面的第一种的问题解析,这里不再重复。

       虽然这种方法目前还没有被广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics