设计模式(单例模式)- 如何设计一个启动器

本贴最后更新于 2427 天前,其中的信息可能已经时移俗易

单例模式的理论就不再描述了 以下对自己喜欢的单例模式写法做个总结说明:


常见实现

常见的单例模式写法有多种,甄选之后比较喜欢如下两种:

  • 静态内部类
  • 枚举

静态内部类

来一段静态内部类实现 spring 容器启动器的代码,如何风骚的初始化 spring 容器。

public class Bootstrap {
	
	private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);

	private Bootstrap(){}
	
	private static class LoaderHelper {
		
		private static final AnnotationConfigApplicationContext CONTEXT;

		static {
			CONTEXT = initContext();
		}

		//初始化spring容器
		private static AnnotationConfigApplicationContext initContext() {
			logger.info("开始初始化spring容器。");
			long startTime = Clock.startTime();
			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TookitConfig.class);
			context.registerShutdownHook();
			logger.info("初始化spring容器完成。耗时:{}", Clock.passed(startTime));
			return context;
		}
	}
	
	
	/**
	 * 获取spring容器
	 * @return
	 */
	public static AnnotationConfigApplicationContext getApplicationContext(){
		return LoaderHelper.CONTEXT;
	}
	
	
	/**
	 * 获取spring中的bean
	 * @param beanName 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String beanName) {
		return (T) LoaderHelper.CONTEXT.getBean(beanName);
	}
	
	
	/**
	 * 获取spring中的bean
	 * @param clazz
	 * @return
	 */
	public static <T> T getBean(Class<T> clazz) {
		return LoaderHelper.CONTEXT.getBean(clazz);
	}

}

JVM 保证了静态内部类被加载时是线程安全的,并且 static 代码块只会被初始化一次,当该类被初始化时,只有调用外部类提供的静态方法时,才会加载内部类,而这些静态方法恰恰需要初始化 spring 容器后调用才有意义,所以这种内部类单例的实现模式可以做到延迟加载。

使用枚举的构造方法实现

public enum BootstrapEnum {

	INSTANCE;
	
	private static final Logger logger = LoggerFactory.getLogger(BootstrapEnum.class);
	
	private AnnotationConfigApplicationContext CONTEXT;

	
	BootstrapEnum() {
		CONTEXT = initContext();
	}
	
	
	private AnnotationConfigApplicationContext initContext() {
		logger.info("开始初始化spring容器。");
		long startTime = Clock.startTime();
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TookitConfig.class);
		context.registerShutdownHook();
		logger.info("初始化spring容器完成。耗时:{}", Clock.passed(startTime));
		return context;
	}
	
	
	/**
	 * 获取spring容器
	 * @return
	 */
	public AnnotationConfigApplicationContext getApplicationContext(){
		return CONTEXT;
	}
	
	/**
	 * 获取spring中的bean
	 * @param beanName 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getBean(String beanName) {
		return (T) CONTEXT.getBean(beanName);
	}
	
	
	/**
	 * 获取spring中的bean
	 * @param clazz
	 * @return
	 */
	public <T> T getBean(Class<T> clazz) {
		return CONTEXT.getBean(clazz);
	}
	
}

相比内部类的实现代码简洁了不少,但是容器的初始化是放在枚举的构造方法里完成的,这样当该枚举类被初始化时,即会加载 spring 容器,这可能不是我希望的。

并且看看调用方式:

//静态内部类
Bootstrap.getApplicationContext();
//枚举形式 这样语意并不是很好
BootstrapEnum.INSTANCE.getApplicationContext();

增加一个静态方法:

public static BootstrapEnum getInstance(){
		return INSTANCE;
}

这样调用方式就变为了:

BootstrapEnum.getInstance().getApplicationContext();

这样看着语意更明确了,即使这样方法的调用却依然比静态内部类实现的单例麻烦(代码长了),虽然单例的实现代码少了,但是每次调用却要多敲代码,不能忍,怎么办?

枚举实现的变种

public enum BootstrapEnum {

	INSTANCE;
	
	private static final Logger logger = LoggerFactory.getLogger(BootstrapEnum.class);
	
	private static AnnotationConfigApplicationContext CONTEXT;
	
	static {
		CONTEXT = initContext();
	}
	
	private static AnnotationConfigApplicationContext initContext() {
		logger.info("开始初始化spring容器。");
		long startTime = Clock.startTime();
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TookitConfig.class);
		context.registerShutdownHook();
		logger.info("初始化spring容器完成。耗时:{}", Clock.passed(startTime));
		return context;
	}
	
	
	/**
	 * 获取spring容器
	 * @return
	 */
	public static AnnotationConfigApplicationContext getApplicationContext(){
		return CONTEXT;
	}
	
	/**
	 * 获取spring中的bean
	 * @param beanName 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String beanName) {
		return (T) CONTEXT.getBean(beanName);
	}
	
	
	/**
	 * 获取spring中的bean
	 * @param clazz
	 * @return
	 */
	public static <T> T getBean(Class<T> clazz) {
		return CONTEXT.getBean(clazz);
	}
	
}

如上,代码的调用变为了:

BootstrapEnum.getApplicationContext();

看着和静态内部类实现的效果差不多了,但是有一个问题还是没能得到解决,就是延迟加载的问题,当该类的静态属性或者方法被访问时,即使不想初始化 spring 容器,static 代码块也会执行。


还有一个严重的问题,此时只需要把该枚举类改成普通类,并删除掉 INSTANCE 就会惊奇的发现,这和恶汉式单例模式无二。代码这样复杂也失去了用枚举类的意义。

结论

当需要延迟加载时选用静态内部类实现,无延迟加载的需求选用枚举的构造方法实现


  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 203 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...