论坛首页 Java企业应用论坛

由destory-method引发的IOC容器设计的思考

浏览 2029 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-12-07   最后修改:2009-12-07
   第一次读Spring的源码大概是半年前写硕士论文的时候,那时侯只是因为想从中看看如何运用设计模式处理特定的问题,同时也对这个被追捧和广泛使用的框架内部的实现很好奇。期间结合着2.5的reference和CodeLogic分析工具看了其IOC,AOP,事务,三方集成的源码,迷迷糊糊的。这样看下来感觉好像知道点什么,但最直接的感受是盲人摸象,知之甚少,有种时间被浪费的感觉。最近对其destory-method的实现产生了好奇,带着问题出发再次看相关的IOC的设计实现,还是得到了一些感悟。

   对于IOC容器的设计,我想多数人的思路都是大同小异的:
  (1)把bean的属性,依赖,特征信息以编码或者文件的形式描述,毕竟要伸手向IOC容器要对象,总要告诉人家你中意的是什么样的吧,呵呵。
  (2)容器开始工作前,把这些bean的信息加载进来仔细的分析,为每个bean所拥有的信息找个存储方式,这时候我想没太多人还选择文件存储了,不然一个bean的信息对应产生一个小文件?主要是编码的方式实现信息存储,也就是new出相应的信息存储辅助对象,Spring中使用BeanDefinition存储每个bean解析后的配置信息。
  (3)这一步对于设计者来说,要首先考虑到是否在容器初始化执行,至少要把开关选择交给用户决定,或者提供多种实现的选择。也就是bean的初始化。毕竟对于实际的项目来说,如果bean过于繁多,特别是仅仅需要测试其中某个模块涉及到的类的功能的话,需要时再初始化是最基础的要求。在Spring中构造bean的工厂有不同的实现选择。使用相关BeanFactory的话,只有getBean(args)的时候才会实例化相应的bean;而使用相关ApplicationContext的话,其默认有refresh()方法会在注册,验证完成之后实例化singleton的bean。通过这三步的实现,IOC容器的基本功能算是具备了,但是多数的singlton的实例,总不能说我给你了,你看着办吧?所以IOC还应该对这些bean生命周期负责,最起码的:我既然能把你造出来,也能把你毁灭。太暴力了,不和谐了...

   具备以上功能的IOC容器才算是一个理论上能用的容器,但是具体的设计时,可扩展性,灵活性,高性能才是衡量一个IOC优劣的重点。目前IOC容器也不少了,被证明能当重任的,除了Spring赖以成名的IOC容器外,还有Goole的Guice,后者说是使用JDK5以后的注释加反射实现,性能比Spring要好很多。没用过,没看过源码,更没有亲自的测试性能,所以没有根据不做评论(接着我会看看其源码的实现,有那么好?)。当然还有广大的程序员们自己根据项目的需求实现的IOC容器,但是不管怎么说,实现原理应该和上面总结的有大面积交集吧?

   下面看Spring,在为具体bean配置信息时,我们可以定义destory-method用于当bean被销毁时需要执行的方法。对于一些占用资源的bean是需要定义释放资源的实现的。但是这个method的名字我们可以随便的根据业务去命名,IOC容器是怎么去调用的呢?我想第一反应应该是反射吧,最终实现是用的反射,但是其内部如何做到设计的统一才是重点。下面具体的结合相应的实现代码去看看。

   对于一个bean的实例化过程,个人认为Spring是划分的非常细致:
  (1)设置对象的属性,具体的实现挺复杂的
  (2)a.检查是否实现Aware相关的接口,设置具体的依赖对象;b.应用BeanPostProcessor的前置处理;c.初始化方法的处理;d.应用BeanPostProcessor后置处理。这四个小过程在AbstractAutowireCapableBeanFactory的createBean()-->doCreateBean()-->initializeBean()方法中实现。其中的c步骤是在invokeInitMethods()里判断到底是bean实现了InitializingBean接口,调用其afterPropertiesSet()还是直接反射调用init-method方法(如果有...)。
  (3)把bean注册成可销毁的(disposable),通过doCreateBean()-->registerDisposableBeanIfNecessary()方法,这样就算完成了一个bean的实例初始化。
   精细的过程保证了其强大的灵活性和可扩展性,利用BeanPostProcessor扩展可以在最终的bean诞生前改变bean的属性,Spring的IOC提供多个改变的机会。个人认为想自己设计IOC容器的,或者正在设计IOC容器的,把关注点放在是使用xml,properties描述呢,还是json,java等等描述信息的存储上,如果能提高性能得或者满足动态修改得话固然值得。但是更要关注的应该是划分精细,层次分明的实例化的过程和销毁的过程。这直接决定你的IOC容器是否拥有强大的生命力,总不能满足不了改变和扩展而重新架构吧(重构部分代码到无妨,但是重打地基只能说明设计的失败)...

  接着从小见大,初窥Spring的IOC局部设计。回到destory-method上来,都知道使用反射调用方法,但是Spring还有一个侵入性强的方案,直接实现DisposableBean接口,此接口提供destory()方法,所以这就需要把两者统一,最好的办法是当销毁bean的时候统一的循环调用销毁bean的destory()方法。到这里自然的想到把我们的bean适配成DisposableBean注册到容器中,在其destory()中完成自定义销毁方法的调用,所以很自然的使用了适配器模式。而AbstractBeanFactory的实现:
 
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
		if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
			if (mbd.isSingleton()) {
				registerDisposableBean(beanName,
						new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors()));
			}
			else {
				// A bean with a custom scope...
				Scope scope = (Scope) this.scopes.get(mbd.getScope());
				if (scope == null) {
					throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'");
				}
				scope.registerDestructionCallback(beanName,
						new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors()));
			}
		}
	}

这就完成了统一,不管是singleton还是自定义的scope,bean都会有DisposableBeanAdapter实现了DisposableBean接口被注册到容器,当需要销毁bean的时候,总有最后调用DisposableBean的destory()。
  
public void destroy() {
		if (this.beanPostProcessors != null && !this.beanPostProcessors.isEmpty()) {
			for (int i = this.beanPostProcessors.size() - 1; i >= 0; i--) {
				((DestructionAwareBeanPostProcessor) this.beanPostProcessors.get(i)).postProcessBeforeDestruction(
						this.bean, this.beanName);
			}
		}

		boolean isDisposableBean = (this.bean instanceof DisposableBean);
		if (isDisposableBean && this.invokeDisposableBean) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");
			}
			try {
				((DisposableBean) this.bean).destroy();
			}
			catch (Throwable ex) {
				String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
				if (logger.isDebugEnabled()) {
					logger.warn(msg, ex);
				}
				else {
					logger.warn(msg + ": " + ex);
				}
			}
		}

		if (this.destroyMethodName != null && !(isDisposableBean && "destroy".equals(this.destroyMethodName))) {
			invokeCustomDestroyMethod();
		}
	}

这个方法完成了销毁动作的统一:1. 已注入的DestructionAwareBeanPostProcessor的postProcessBeforeDestruction()方法,搞点销毁前的小动作;2.如果bean本身就是DisposableBean的实现,调用它的destory()方法,搞点销毁前的小动作;3.如果不是并且有自定义的销毁方法,反射它搞点销毁前的小动作。总而言之,不能轻易的say byebye...

   在Spring中时而统一,时而分离都是根据具体的场景需求选择适合自己的设计解决问题。或许有为了设计而设计的,希望我能找的到,呵呵。总的来说IOC容器本身个人认为没有什么神秘的,但是为了具体的目标而实现的IOC容器在设计上自认远远不能。另外关于开源项目的源码,本人也扫了不少的不少眼,有点看源码的体会:(1)首先源码拿到要看包结构,从包结构设计完全能反映一个项目的好坏,包结构设计凌乱,类的划分界限不明确的要具体对待了。(2)找个切入点,这个不难的。比如你想看tomcat怎么部署app的?Spring中怎么getBean()的?一句话,带着问题进入相应的包,相应的模块。(3)在源码自建一个测试包,写写简单的调用,主要使用IDE的debug看过程。纯代码能看懂功能,不一定能看懂实现,几次断点跟踪一定找到你想看到的逻辑。(4)看到觉得比较闪的实现,试着模拟一下,想想自己会怎么设计等等。写的有点多,哎...
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics