Spring IoC之bean的一生

接着上一篇

1.bean的实例化与BeanWrapper

容器在内部实现的时候,采用“策略模式”来决定采用何种方式初始化bean实例。通常可以通过反射或者CGLIB动态字节码来初始化相应的bean实例或者动态生成其子类。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。容器只要根据相应bean定义的BeanDefinition取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。

至此,第一步结束。

BeanWrapper接口通常在Spring框架内部使用,它有一个实现类org.springframework.beans.BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。 而在第一步结束之后返回BeanWrapper实例而不是原先的对象实例,就是为了第二步“设置对象属性” 。使用BeanWrapper对bean实例操作很方便,可以免去直接使用Java反射API操作对象的繁琐。

2.各色的Aware接口

当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。

1.org.springframework.beans.factory.BeanNameAware:如果Spring容器检测到当前对象实现了该接口,将该对象实例的bean定义的beanName设置到当前对象实例。

2.org.springframework.beans.factory.BeanClassLoaderAware:如果Spring容器检测到当前对象实现了该接口,将对应加载当前bean的Classloader注入当前对象实例。默认会使用加载org.springframework.util.ClassUtils类的Classloader。

3.org.springframework.beans.factory.BeanFactoryAware:如果Spring容器检测到当前对象实现了该接口,BeanFactory容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。

以上几个几口只是针对 BeanFactory 类型的容器而言,对于ApplicationContext类型的容器,也存在几个Aware相关接口。不过在检测这些接口并设置相关依赖的实现原理上,使用的是BeanPostProcessor方式。

4.org.springframework.context.ResourceLoaderAware:将当前ApplicationContext自身设置到对象实例。这样当前对象实例就拥有了其所在ApplicationContext容器的一个引用。

5.org.springframework.context.ApplicationEventPublisherAware:ApplicationContext作为容器同时还实现了ApplicationEventPublisherAware接口,这样,它就可以作为ApplicationEventPublisher来使用。所以,如果当前对象实现了这个接口,则会将自身注入到当前对象。

6.org.springframework.context.MessageSourceAware:ApplicationContext作为容器同时还实现了MessageSource接口来提供国际化支持。所以,如果当前对象实现了这个接口,则会将自身注入到当前对象。

7.org.springframework.context.ApplicationContextAware:将自身注入到当前对象实例。

3.BeanPostProcessor

BeanPostProcessor和BeanFactoryPostProcessor的概念容易混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段。

该接口声明了两个方法,分别在两个不同的时机执行,见代码定义如下:

public interface BeanPostProcessor{
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
复制代码

两个方法分别对应了前置处理和后置处理。BeanPostProcessor的两个方法中都传入了原来的对象实例的引用,这为我们扩展容器的对象实例化过程中的行为提供了极大的便利,我们几乎可以对传入的对象实例执行任何的操作。

通常比较常见的的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供代理实现。在上图中ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,ApplicationContext容器会检测到之前注册到容器的ApplicationContextAwarePorcessor这个BeanPostProcessor的实现类,然后就会调用其postProcessBeforeInitialization()方法,检查并设置Aware相关依赖。ApplicationContextAwareProcessor的postProcessorBeforeInitialization()代码如下:

public Object postProcessBeforeInitialization(Object bean, String beanName) throw BeansException {
    if (bean instanceof ResourceLoaderAware){
        ((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext);
    }
    if (bean instanceof ApplicationEventPublisherAware){
        ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);
    }
    if (bean instanceof MessageSourceAware){
        ((MessageSourceAware)bean).setMessageSource(this.applicationContext);
    }
    if (bean instanceof ApplicationContextAware){
        ((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
    }
    return bean;
}
复制代码

除了检查标记接口以便应用自定义逻辑,还可以通过BeanPostProcessor对当前对象实例做更多的处理。比如替换当前对象实例或者字节码增强当前对象实例等。Spring的AOP则更多地使用BeanPostProcessor来为对象生成相应的代理对象。

举一个BeanPostProcessor的使用场景:假设系统中所有的IFXNewsListener实现类需要从某个位置取得相应的服务器连接密码,而且系统中保存的密码是加密的,那么在IFXNewsListener发送这个密码给新闻服务器进行连接验证的时候,首先需要对系统中取得的密码进行解密,然后才能发送,我们采用BeanPostProcessor技术,对所有的IFXNewsListener的实现类进行统一的解密操作。

第一步:标注需要进行解密的实现类

public interface PasswordDecodable{
    String getEncodedPassword();
    void setDecodedPassword(String password);
}
复制代码
public class DowJonesNewsListener implements IFXNewsListener, PasswordDecodeable{
    private String password;
    ...
    public String getEncodedPassword(){
        return this.password;
    }
    public void setDecodedPassword(String password){
        this.password = password;
    }
}
复制代码

第二步:实现相应的BeanPostProcessor对符合条件的Bean实例进行处理。通过PasswordDecodable接口声明来区分将要处理的对象实例。

public class PasswordDecodePostProcessor implements BeanPostProcessor{
    public Object postProcessAfterInitialization(Object object, String beanName) throws BeansException{
        return object;
    }
    public Object postProcessBeforeInitialization(Object object, String beanName) throws BeansException{
        if (object instanceof PasswordDecodable){
            String encodedPassword = (PasswordDecodable)object.getEncodedPassword();
            String decodedPassword = decodePassword(encodedPassword);
            (PasswordDecodable)object.setDecodedPassword(decodedPassword);
        }
        return object;
    }
    private String decodePassword(String encodedPassword){
        //实现解码逻辑
        ...
        return encodedPassword;
    }
}
复制代码

第三步:将自定义的BeanPostProcessor注册到容器

注意实际上,有一种特殊类型的BeanPostProcessor没有提到,它的执行时机与通常的BeanPostProcessor不同。

org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口可以在对象的实例化过程中导致某种类似于电路“短路”的效果。实际上,并非所有注册到Spring容器内的bean定义都是按照之前的bean实例化那个流程图的。在所有的步骤之前,也就是实例化bean对象之前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。如果有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。构造成功后直接返回构造完成的对象实例,而不会按照“正规的流程”继续执行。这就是它可能造成“短路”的原因。

4.InitializingBean和init-method

org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口,其定义如下:

public interface InitializingBean{
    void afterPropertiesSet() throws Exception;
}
复制代码

该接口定义很简单,其作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。

虽然该接口在Spring内部广泛使用,但如果真的让我们的业务对象实现这个接口,则显得Spring容器比较具有侵入性。所以,Spring还提供了另一种方式来指定自定义的对象初始化操作,那就是在XML配置的时候,使用  的init-method属性。

5.DisposableBean与destroy-method

与InitializingBean和init-method用于对象的自定义初始化相对应,DisposableBean和destroy-method为对象提供了执行自定义销毁逻辑的机会。最常见到的该功能的使用场景就是在Spring容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。

不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用时,才会执行相关的自定义销毁逻辑。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。

对于BeanFactory容器来说,我们需要在独立应用程序的主程序退出之前,调用ConfigurableBeanFactory提供的destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例。如果不能在合适的实际调用destroySingletons(),那么所有实现了DisposableBean接口的对象实例或者声明了destroy-method的bean定义对应的对象实例,他们的自定义对象销毁逻辑就形同虚设,根本不会执行!

对于ApplicationContext容器来说,道理是一样。但AbstractApplicationContext为我们提供了registerShutdownHook()方法,该方法底层使用标准的Runtime类的addShutdownHook()方式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singleton类型的bean对象实例的自定义销毁逻辑会被执行。

至此,bean走完了它在容器中“光荣”的一生。

需要资料的小伙伴关注我之后添加小助理vx:bjmsb6666 即可获取免费下载方式

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值