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

本文深入探讨了Spring框架中IOC容器的设计与实现细节,重点分析了destory-method的实现机制,包括bean的生命周期管理、适配器模式的应用及反射调用方法等内容。
第一次读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)看到觉得比较闪的实现,试着模拟一下,想想自己会怎么设计等等。写的有点多,哎...
<think>我们正在处理用户的问题:SpringXML配置中不允许使用destroy-method属性。用户在使用Spring框架时遇到了这个问题,需要了解原因和解决方案。根据用户的问题,我们首先需要明确:在Spring的XML配置中,<bean>元素通常支持destroy-method属性,用于指定在bean销毁时调用的方法。但是,用户遇到了不允许使用的情况,可能是由于某些配置或环境限制。接下来,我们分析可能的原因:1.**XMLSchema定义的限制**:如果用户使用的XMLSchema版本不支持destroy-method属性,或者命名空间声明不正确,可能导致解析错误。2.**Bean定义的类型**:如果bean不是由Spring容器直接管理(例如,它是通过工厂方法创建的),可能会有限制。3.**配置位置**:destroy-method属性通常用于<bean>元素,但如果在错误的元素上使用(比如在<property>内部),则不允许。另外,参考用户提供的引用内容:-引用[1]提到了ContextNamespaceHandler,这通常用于处理<context:>命名空间下的元素,与<bean>元素不同。-引用[2]和[3]是关于基于Java的配置和属性扫描,与XML配置中的destroy-method属性没有直接关系。因此,我们重点分析XML配置中的<bean>元素。**解决方案步骤:**1.**检查XML命名空间和Schema定义**:确保XML文件的头部声明了正确的命名空间,并且包含了beans的Schema定义。正确的声明应该类似于:```xml<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">```如果缺少正确的Schema定义,可能导致属性不被识别。2.**确认destroy-method的使用位置**:destroy-method是<bean>元素的属性,例如:```xml<beanid="exampleBean"class="com.example.ExampleBean"destroy-method="cleanup"/>```确保它被用在<bean>标签上,而不是其他标签。3.**检查IDE或编辑器的问题**:有时候,IDE的XML校验可能因为缓存或配置问题而错误地标记属性为不允许。尝试清理项目或重新导入。4.**Spring版本兼容性**:虽然destroy-method是一个长期存在的属性,但极低版本的Spring可能不支持,但这种情况很少见。确保使用的Spring版本在3.0以上(通常都支持)。5.**替代方案**:如果确实无法使用destroy-method属性,可以考虑使用其他方式实现销毁方法:-实现`DisposableBean`接口,并实现`destroy()`方法。-使用JSR-250的`@PreDestroy`注解(需要启用注解扫描):```xml<context:annotation-config/>```然后在bean的方法上加上`@PreDestroy`。6.**查看错误信息**:如果用户得到的是具体的错误信息(如编译错误或部署错误),请根据错误信息进一步诊断。例如,如果错误信息是“Attribute'destroy-method'isnotallowed”,则很可能是XML结构问题。**总结**:用户遇到destroy-method属性不允许使用的问题,首先需要检查XML的命名空间和Schema定义是否正确,确保destroy-method被用在<bean>标签上。如果问题仍然存在,可以考虑使用替代方案(如实现DisposableBean接口或使用@PreDestroy注解)。最后,根据指令,我们需要生成相关问题:§§相关问题§§1.如何正确配置SpringXML中的bean销毁方法?2.Spring中除了destroy-method,还有哪些方式可以定义bean的销毁方法?3.为什么在SpringXML配置中会出现属性不被允许的错误?</think>### Spring XML配置中destroy-method属性不允许的原因与解决方案 在Spring XML配置中,`destroy-method`属性通常用于指定bean销毁时调用的方法。若遇到该属性不被允许的情况,主要可能由以下原因及解决方案: #### 原因分析 1. **XML Schema验证失败** - 当XML头部命名空间声明或Schema定义不完整时,IDE/解析器会严格校验属性合法性。 - 示例错误:缺少`spring-beans`的Schema定义: ```xml <!-- 错误示例:缺少xsi:schemaLocation中的spring-beans定义 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ``` 2. **作用域限制** - `prototype`作用域的bean默认不执行销毁方法,显式声明`destroy-method`可能被IDE标记为冗余。 3. **注解配置冲突** - 若同时使用`@PreDestroy`注解和XML配置的`destroy-method`,可能触发校验警告。 #### 解决方案 1. **完善XML Schema定义** 确保`xsi:schemaLocation`包含完整的`spring-beans`定义: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myBean" class="com.example.MyBean" destroy-method="cleanup"/> </beans> ``` 2. **使用替代销毁机制** - **实现`DisposableBean`接口**(强制实现`destroy()`方法): ```java public class MyBean implements DisposableBean { @Override public void destroy() { /* 清理逻辑 */ } } ``` - **JSR-250 `@PreDestroy`注解**(需启用注解扫描): ```xml <context:annotation-config/> ``` ```java public class MyBean { @PreDestroy public void cleanup() { ... } } ``` 3. **检查作用域配置** 对`prototype`作用域的bean,销毁方法需通过其他方式触发(如手动调用`ConfigurableBeanFactory.destroyBean()`)。 #### 最佳实践建议 - 优先使用`@PreDestroy`注解,与组件扫描更兼容[^3]。 - 对于第三方库的bean(无法修改源码),再使用XML的`destroy-method`。 - 始终验证XML Schema定义,避免解析错误[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值