Spring技术内幕——IOC部分摘录


哪些方面的控制被反转了?(名词“依赖注入”的由来)



Spring IOC的容器设计中,有两种主要的容器系列,一个是实现BeanFactory接口的简单容器系列;另一个是ApplicationContext应用上下文,增加了许多面向框架的特性同时对应用环境做了适配。


(下图中全是接口)


三套接口体系: 
1、从接口BeanFactory道HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径 
2、以ApplicationContext应用上下文接口为核心的接口设计 
3、整套接口系统以BeanFactory和ApplicationContext为核心的。 


BeanFactory的应用场景(现在基本被废弃,了解下设计原理)

用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分通过容器来获取FactoryBean产生的对象和获取FactoryBean本身。举例来说,如果myJndiObject是一个FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJndiObject这个FactoryBean产生出来的对象。


注: 理解上面这段话需要很好地区分FactoryBean和BeanFactory这两个在Spring中使用频率很高的类。一个是Factory,也就是IoC容器或对象工厂;一个是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。


ApplicationContext容器的应用场景

支持不同的信息源

访问资源

支持应用事件

在ApplicationContext中提供的附加服务。所以一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式。


IOC容器的初始化过程

简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IoC容器的正式启动。具体来说,这个启动包括BeanDefinition的Resouce定位、载入和注册三个基本过程。

第一个过程是Resource定位过程。这个Resource定位指的是BeanDefinition的资源定位。

第二个过程是BeanDefinition的载入。这个载入过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。

第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过分析,我们可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。


在这个过程中,一般不包含Bean依赖注入的实现。在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。


初始化过程部分摘要:http://blog.csdn.net/qqqqq1993qqqqq/article/details/74928245


IOC容器的依赖注入

假设当前IoC容器已经载入了用户定义的Bean信息,开始分析依赖注入的原理。首先,注意到依赖注入的过程是用户第一次向IoC容器索要Bean时触发的,当然也有例外,也就是我们可以在BeanDefinition信息中通过控制lazy-init属性来让容器完成对Bean的预实例化。这个预实例化实际上也是一个完成依赖注入的过程,但它是在初始化的过程中完成的,稍后我们会详细分析这个预实例化的处理。当用户向IoC容器索要Bean时,如果读者还有印象,那么一定还记得在基本的IoC容器接口BeanFactory中,有一个getBean的接口定义,这个接口的实现就是触发依赖注入发生的地方。

依赖注入部分摘要:http://blog.csdn.net/qqqqq1993qqqqq/article/details/77887235


容器其他相关特性的实现

对ApplicationContext启动的过程是在AbstractApplicationContext中实现的。在使用应用上下文时需要做一些准备工作,这些准备工作在prepareBeanFactory()方法中实现。在这个方法中,为容器配置了ClassLoader、PropertyEditor和BeanPost-Processor等,从而为容器的启动做好了必要的准备工作。


同样,在容器要关闭时,也需要完成一系列的工作,这些工作在doClose( )方法中完成。在这个方法中,先发出容器关闭的信号,然后将Bean逐个关闭,最后关闭容器自身。


容器的实现是通过IoC管理Bean的生命周期来实现的。Spring IoC容器在对Bean的生命周期进行管理时提供了Bean生命周期各个时间点的回调。在分析Bean初始化和销毁过程的设计之前,简要介绍一下IoC容器中的Bean生命周期。

  1. Bean实例的创建。
  2. 为Bean实例设置属性。
  3. 调用Bean的初始化方法。
  4. 应用可以通过IoC容器使用Bean。
  5. 当容器关闭时,调用Bean的销毁方法。
Bean的初始化方法调用是在以下的initializeBean方法中实现的,doGetBean方法调用creatBean时(此时会有依赖注入),creatBean也调用了此方法。

在调用Bean的初始化方法之前,会调用一系列的aware接口实现,把相关的BeanName、BeanClassLoader,以及BeanFactoy注入到Bean中去。接着会看到对invokeInitMethods的调用,这时还会看到启动afterPropertiesSet的过程,当然,这需要Bean实现InitializingBean的接口,对应的初始化处理可以在InitializingBean接口的afterPropertiesSet方法中实现,这里同样是对Bean的一个回调。

最后,还会看到判断Bean是否配置有initMethod,如果有,那么通过invokeCustom-InitMethod方法来直接调用,最终完成Bean的初始化。
在这个对initMethod的调用中,可以看到首先需要得到Bean定义的initMethod,然后通过JDK的反射机制得到Method对象,直接调用在Bean定义中声明的初始化方法。

lazy-init属性和预实例化(?)
应该是指非懒加载的bean,在refresh方法中的finishBeanFactoryInitialization(beanFactory); 时就已经完成了getBean(),也就是说将依赖注入的操作提前了。

BeanPostProcessor的实现

BeanPostProcessor是使用IoC容器时经常会遇到的一个特性,这个Bean的后置处理器是一个监听器,它可以监听容器触发的事件。将它向IoC容器注册后,容器中管理的Bean具备了接收IoC容器事件回调的能力。BeanPostProcessor的使用非常简单,只需要通过设计一个具体的后置处理器来实现。同时,这个具体的后置处理器需要实现接口类BeanPostProcessor,然后设置到XML的Bean配置文件中。这个BeanPostProcessor是一个接口类,它有两个接口方法,一个是postProcessBeforeInitialization,在Bean的初始化前提供回调入口;一个是postProcessAfterInitialization,在Bean的初始化后提供回调入口,这两个回调的触发都是和容器管理Bean的生命周期相关的。这两个回调方法的参数都是一样的,分别是Bean的实例化对象和Bean的名字。BeanPostProcessor为具体的处理提供基本的回调输入。

//初始化Bean实例,调用在容器的回调方法和Bean的初始化方法  
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {  
    if (bean instanceof BeanNameAware) {  
        ((BeanNameAware) bean).setBeanName(beanName);  
    }  
    if (bean instanceof BeanClassLoaderAware) {  
        ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());  
    }  
    if (bean instanceof BeanFactoryAware) {  
        ((BeanFactoryAware) bean).setBeanFactory(this);  
    }  
    //这里是对后置处理器BeanPostProcessors的postProcessBeforeInitialization的  
    //回调方法的调用  
    Object wrappedBean = bean;  
    if (mbd == null || !mbd.isSynthetic()) {  
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);  
    }  
    //调用Bean的初始化方法,这个初始化方法是在BeanDefinition中通过定义init-method属性指定的  
    //同时,如果Bean实现了InitializingBean接口,那么这个Bean的afterPropertiesSet  
    //实现也会被调用  
    try {  
        invokeInitMethods(beanName, wrappedBean, mbd);  
    }  
    catch (Throwable ex) {  
        throw new BeanCreationException(  
                 (mbd != null ? mbd.getResourceDescription() : null),  
                 beanName, "Invocation of init method failed", ex);  
    }  
    //这里是对后置处理器BeanPostProcessors的postProcessAfterInitialization的回调方  
    //法的调用  
    if (mbd == null || !mbd.isSynthetic()) {  
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);  
    }  
    return wrappedBean;  
}  
/*这里是对设置好的BeanPostProcessors的postProcessBeforeInitialization回调进行依次调用的地方*/  
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean,  
String beanName)  
        throws BeansException {  
    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {  
        result = beanProcessor.postProcessBeforeInitialization(result, beanName);  
    }  
    return result;  
}  
//这里是对设置好的BeanPostProcessors的postProcessAfterInitialization回调进行  
//依次调用的地方  
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)  
    throws BeansException {  
    Object result = existingBean;  
    for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {  
        result = beanProcessor.postProcessAfterInitialization(result, beanName);  
    }  
    return result;  
} 

 autowiring(自动依赖装配)的实现

在前面对IoC容器实现原理的分析中,一直是通过BeanDefinition的属性值和构造函数以显式的方式对Bean的依赖关系进行管理的。在Spring中,相对这种显式的依赖管理方式,IoC容器还提供了自动依赖装配的方式,为应用使用容器提供更大的方便。在自动装配中,不需要对Bean属性做显式的依赖关系声明,只需要配置好autowiring属性,IoC容器会根据这个属性的配置,使用反射自动查找属性的类型或者名字,然后基于属性的类型或名字来自动匹配IoC容器中的Bean,从而自动地完成依赖注入。
这是一个很有诱惑力的功能特性,使用它可以完成依赖关系管理的自动化,但是使用时一定要注意,计算机只是在自动执行,它是不会思考的。使用这个特性的优点是能够减少用户配置Bean的工作量,但它是一把双刃剑,如果使用不当,也会为应用带来不可预见的后果,所以,使用时需要多一些小心和谨慎。
从autowiring使用上可以知道,这个autowiring属性在对Bean属性进行依赖注入时起作用。对Bean属性依赖注入的实现原理,在前面已经做过分析。回顾那部分内容,不难发现,对autowirng属性进行处理,从而完成对Bean属性的自动依赖装配,是在populateBean中实现的。节选AbstractAutowireCapableBeanFactory的populateBean方法中与autowiring实现相关的部分,可以清楚地看到这个特性在容器中实现的入口。也就是说,对属性autowiring的处理是populateBean处理过程的一个部分。在populateBean的实现中,在处理一般的Bean之前,先对autowiring属性进行处理。如果当前的Bean配置了autowire_by_name和autowire_by_type属性,那么调用相应的autowireByName方法和autowireByType方法。这两个方法很巧妙地应用了IoC容器的特性。例如,对于autowire_by_name,它首先通过反射机制从当前Bean中得到需要注入的属性名,然后使用这个属性名向容器申请与之同名的Bean,这样实际又触发了另一个Bean的生成和依赖注入的过程。实现过程如代码清单2-35所示。

//开始进行依赖注入过程,先处理autowiring的注入  
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||  
    mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  
    MutablePropertyValues newnewPvs = new MutablePropertyValues(pvs);  
    // 这里是对autowire注入的处理,根据Bean的名字或者type进行autowire的过程  
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {  
        autowireByName(beanName, mbd, bw, newPvs);  
    }  
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  
        autowireByType(beanName, mbd, bw, newPvs);  
    }  
    pvs = newPvs;  
} 
在对autowiring类型做了一些简单的逻辑判断以后,通过调用autowireByName和autowireByType来完成自动依赖装配。以autowireByName为例子来看看容器的自动依赖装配功能是怎样实现的。对autowireByName来说,它首先需要得到当前Bean的属性名,这些属性名已经在BeanWrapper和BeanDefinition中封装好了,然后是对这一系列属性名进行匹配的过程。在匹配的过程中,因为已经有了属性的名字,所以可以直接使用属性名作为Bean名字向容器索取Bean,这个getBean会触发当前Bean的依赖Bean的依赖注入,从而得到属性对应的依赖Bean。在执行完这个getBean后,把这个依赖Bean注入到当前Bean的属性中去,这样就完成了通过这个依赖属性名自动完成依赖注入的过程。autowireByType的实现和autowireByName的实现过程是非常类似的,感兴趣的读者可以自己进行分析。这些autowiring的实现如代码清单2-36所示。

protected void autowireByName(  
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw,  
MutablePropertyValues pvs) {  
    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);  
    for (String propertyName : propertyNames) {  
        if (containsBean(propertyName)) {  
            //使用取得的当前Bean的属性名作为Bean的名字,向IoC容器索取Bean  
            //然后把从容器得到的Bean设置到当前Bean的属性中去  
            Object bean = getBean(propertyName);  
            pvs.addPropertyValue(propertyName, bean);  
            registerDependentBean(propertyName, beanName);  
            if (logger.isDebugEnabled()) {  
                logger.debug(  
                    "Added autowiring by name from bean name '" + beanName + "'  
                    via property '" + propertyName +  
                    "' to bean named '" + propertyName + "'");  
            }  
        }  
        else {  
            if (logger.isTraceEnabled()) {  
                logger.trace("Not autowiring property '" + propertyName + "' of  
                bean '" + beanName +  
                    "' by name: no matching bean found");  
            }  
        }  
    }  
} 
 Bean的依赖检查
在使用Spring的时候,如果应用设计比较复杂,那么在这个应用中, IoC管理的Bean的个数可能非常多,这些Bean之间的相互依赖关系也会非常复杂。在一般情况下,Bean的依赖注入是在应用第一次向容器索取Bean的时候发生,在这个时候,不能保证注入一定能够成功,如果需要重新检查这些依赖关系的有效性,会是一件很繁琐的事情。为了解决这样的问题,在Spring IoC容器中,设计了一个依赖检查特性,通过它,Spring可以帮助应用检查是否所有的属性都已经被正确设置。在具体使用的时候,应用只需要在Bean定义中设置dependency-check属性来指定依赖检查模式即可,这里可以将属性设置为none、simple、object、all四种模式,默认的模式是none。如果对检查模式进行了设置,通过下面的分析,可以更好地理解这个特性的使用。具体的实现代码是在AbstractAutowireCapableBeanFactory实现createBean的过程中完成的。在这个过程中,会对Bean的Dependencies属性进行检查,如果发现不满足要求,就会抛出异常通知应用。


Bean对IoC容器的感知

容器管理的Bean一般不需要了解容器的状态和直接使用容器,但在某些情况下,是需要在Bean中直接对IoC容器进行操作的,这时候,就需要在Bean中设定对容器的感知。Spring IoC容器也提供了该功能,它是通过特定的aware接口来完成的。aware接口有以下这些:

BeanNameAware ,可以在Bean中得到它在IoC容器中的Bean实例名称。

BeanFactoryAware,可以在Bean中得到Bean所在的IoC容器,从而直接在Bean中使用IoC容器的服务。

ApplicationContextAware,可以在Bean中得到Bean所在的应用上下文,从而直接在Bean中使用应用上下文的服务。

MessageSourceAware,在Bean中可以得到消息源。

ApplicationEventPublisherAware,在Bean中可以得到应用上下文的事件发布器,从而可以在Bean中发布应用上下文的事件。

ResourceLoaderAware,在Bean中可以得到ResourceLoader,从而在Bean中使用ResourceLoader加载外部对应的Resource资源。


小结


在本章中,为了说明Spring的实现原理,我们紧密结合Spring的源代码,对容器的实现原理进行了详细的分析,旨在为读者整理出一条清晰的线索。其中包括IoC容器和上下文的基本工作原理、容器的初始化过程、依赖注入的实现,等等。总地来说,关于容器的基本工作原理,可以大致整理出以下几个方面:
BeanDefinition的定位。对IoC容器来说,它为管理POJO之间的依赖关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。我们可以使用各种形式的Bean定义信息,其中比较熟悉和常用的是使用XML的文件格式。在Bean定义方面,Spring为用户提供了很大的灵活性。在初始化IoC容器的过程中,首先需要定位到这些有效的Bean定义信息,这里Spring使用Resource接口来统一这些Bean定义信息,而这个定位由ResourceLoader来完成。如果使用上下文,ApplicationContext本身就为客户提供了定位的功能。因为上下文本身就是DefaultResourceLoader的子类。如果使用基本的BeanFactory作为IoC容器,客户需要做的额外工作就是为BeanFactory指定相应的Resource来完成Bean信息的定位。
容器的初始化。在使用上下文时,需要一个对它进行初始化的过程,完成初始化以后,这个IoC容器才是可用的。这个过程的入口是在refresh中实现的,这个refresh相当于容器的初始化函数。在初始化过程中,比较重要的部分是对BeanDefinition信息的载入和注册工作。相当于在IoC容器中需要建立一个BeanDefinition定义的数据映像,Spring为了达到载入的灵活性,把载入的功能从IoC容器中分离出来,由BeanDefinitionReader来完成Bean定义信息的读取、解析和IoC容器内部BeanDefinition的建立。在DefaultListableBeanFactory中,这些BeanDefinition被维护在一个Hashmap中,以后的IoC容器对Bean的管理和操作就是通过这些BeanDefinition来完成的。
在容器初始化完成以后,IoC容器的使用就准备好了,但这时只是在IoC容器内部建立了BeanDefinition,具体的依赖关系还没有注入。在客户第一次向IoC容器请求Bean时,IoC容器对相关的Bean依赖关系进行注入。如果需要提前注入,客户可以通过lazy-init属性进行预实例化,这个预实例化是上下文初始化的一部分,起到提前完成依赖注入的控制作用。在依赖注入完成以后,IoC容器就会保持这些具备依赖关系的Bean供客户直接使用。这时可以通过getBean来取得Bean,这些Bean不是简单的Java对象,而是已经包含了对象之间依赖关系的Bean,尽管这些依赖注入的过程对用户来说是不可见的。
在对IoC容器的分析中,重点讲解了BeanFactory和ApplicationContext体系、ResourceLoader、refresh初始化、容器的loadBeanDefinition和注册、容器的依赖注入、预实例化和FactoryBean的工作原理,等等。通过对这些实现过程的深入分析,我们可以初步了解IoC容器的基本工作原理和它的基本特性的实现思路。了解了IoC容器的基本实现原理后,我们对容器的其他特性的实现原理也进行了分析。这些特性包括init-lazy预实例化、BeanFactory、Bean后置处理器以及autowiring特性的实现。这些特性对我们更灵活地使用IoC容器有很大的帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值