1

IoC就是交易

IoCInvert Of Control,控制反转)的概念其实出现的蛮早的,但是由于概念枯燥难懂所以被接受的程度一直不高,老头子灵机一动,为亲儿子Spring重新起了一个名字DIDependency Inject,依赖注入)后,总算有人请他出山了。

DI其实也不高端,本质就是交易,人类自从脱离刀耕火种后已经用了好几千年了,只不过因为当时软件编程还处于个人英雄主义时代,所以对其并不重视。

DI:啤酒不是客人自己造出来的,而是服务员塞给他的,客人并不关心啤酒是怎么来的,他只关心这是我要的啤酒吗?

Bean放在IoC容器中

Spring通过ApplicationContext(应用上下文)来管理下属所有的IoC容器(Spring中称为BeanFactory),容器中堆放了所有需要管理的实例,这些实例被老头子称为Bean。容器会自动地提供用户程序所需要的实例,只要“客户”通过Spring规定的方式予以告知,其他诸如实例的生命周期、如何生产实例之类的事情全由容器代劳。

说句题外话,Bean这个名字在当时被深深地吐槽,群众们纷纷批评老头子不要B脸,居然蹭当时如日中天的企业级应用的Top解决方案JavaEE的热点,直接抄袭其命名创意。不过时光荏苒,谁能想到10年之后,Bean这个词语已然被Spring垄断。

下面进入主线:BeanFactory是怎么服务“客户”程序的?下面分为两个状态分析

第一态:BeanFactory初始化时

差钱企业普遍使用的Tomcat服务器 + MyBatis/iBatis+Spring+Struts/SpringMVC的Web三贱客为例,Tomcat启动时会拉起Spring的WebApplicationContext,并调用其refresh方法。也可以脱离服务器环境,直接执行new ClassPathXmlApplicationContext("spring.xml"),同样会调用refresh方法。

refresh中与IoC密切相关的操作共有5步,我把重要的用粗体标出来:

  1. obtainFreshBeanFactory 获得新的BeanFactory
  2. prepareBeanFactory 一些配置
  3. invokeBeanFactoryPostProcessors 处理BeanFactory的回调
  4. registerBeanPostProcessors 注册Bean的回调
  5. finishBeanFactoryInitialization 初始化所有非懒加载的单例

refresh完成后,IoC容器中已经堆放了必要的实例,可以对外提供服务了。

obtainFreshBeanFactory:解析XML、Java类,并加载BeanDefinition(Bean定义)

在Web项目中,通常用户指定的Bean定义的方式通常有两种,一种是XML文件配置,另一种是注解。

  • 项目中会配置若干个spring配置文件(xml格式),ApplicationContext启动时会加载并解析xml文件,以appcontext-client.xml为例,文件中自定义了一个id为shopInfoOpenService的Bean,规定了若干property(属性)的值、Bean的生成方式、Bean的初始化方法,这些都属于BeanDefinition。

通过XML配置Spring

   <?xml version="1.0" encoding="UTF-8"?>
   <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       default-lazy-init="true"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd">
   
       <bean id="shopInfoOpenService" class="com.dianping.dpsf.spring.ProxyBeanFactory" init-method="init">
           <property name="serviceName" value="http://service.dianping.com/orderdish/dbh/deploymanage/shopInfoOpenService_1.0.0"/>
           <property name="iface" value="com.dianping.orderdish.dbh.deploymanage.service.ShopInfoOpenService"/>
           <property name="serialize" value="hessian"/>
           <property name="callMethod" value="sync"/>
           <property name="timeout" value="5000"/>
       </bean>
   </beans>
  • 注解的应用更为方便和分散。标注在类上的注解如@Service@Component@Controller@Repository可以方便的标记该类的实例是单例Bean,Spring会扫描指定包名下的所有类,发现这些特定的类并载入其BeanDefinition。

    @Service
    public class QueryShopService {
        @Autowired
        private ShopInfoOpenService shopInfoOpenService;
        
        public ShopListVO queryByShop(QueryShopParam queryShopParam) throws TException {
            return null;
        }
    }

Spring解析加载的过程也分为两步:

  1. 首先解析xml中默认的命名空间下的xml标签。一共有4种:<bean><beans><import><alias>
  2. 接着解析xml中自定义的命名空间下的xml标签。其他所有的标签都在这儿处理,包括对被@Component注解的类的扫描,对被@Autowired注解的成员变量的预处理,下面具体分析一下这个步骤是在什么时候触发的,靠什么机制触发的?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="com.dianping"/>
    <context:annotation-config/>
</beans>

整个动作由xml中的这两个标签触发:<context:component-scan><context:annotation-config/>。在Spring解析自定义命名空间的标签之前,会通过一系列的NamespaceHandler注册相关的BeanDefinitionParser,比如ContextNamespaceHandler就负责注册<context>标签下的所有解析器。<context:component-scan>就由ComponentScanBeanDefinitionParser负责解析,并委托ClassPathBeanDefinitionScanner发现并返回指定包名下所有被@Component标注的BeanDefinition。<context:annotation-config/>AnnotationConfigBeanDefinitionParser负责解析,并委托静态方法AnnotationConfigUtils.registerAnnotationConfigProcessors返回@Autowired@Required等注解的BeanPostProcessor处理器,最后统一注册到容器中。

BeanPostProcessor是一种插件机制,允许用户在Bean的生命周期的各个阶段插入一些自己的操作,甚至改变容器原有的行为。举个例子,用户可以在Bean初始化之前、之后插入自己的行为。

finishBeanFactoryInitialization:初始化非懒加载的Bean单例

该方法遍历容器中所有的BeanDefinition,调用getBean方法获得Bean实例,如果上级容器找不到Bean实例的话,会先生成被当前Bean直接依赖(depends-on)的其他Bean实例,最后调用createBean方法,完成单个Bean的初始化。整个初始化过程分为4步:

  1. Bean的实例化。Java中常见的实例化方法有new ClassA()new ClassA(args...),Spring中的分类略有不同,除了上面的有参构造器和无参构造器,还引入了工厂模式的FactoryBean,即用FactoryBean工厂生成产品Bean的实例。还记得上面名为shopInfoOpenService的Bean吗,这就是FactoryBean的典型使用方式。容器会按照FactoryBean、有参构造器、无参构造器的优先顺序尝试生成Bean实例。
  2. 为Bean实例注入属性。需要注入的属性定义来自xml中的property标签,有两种典型用法:<property name="" value="">或者<property name="" ref="">,前者注入一个基本类型值,后者注入一个Bean实例。这里有一个问题,即如何识别ref字段的值并将其对应到容器中唯一一个Bean实例。Spring提供了两种方案,第一种是按照ref的name,可以唯一匹配或者匹配不到Bean;第二种是按照ref的type,由于存在类型的转化与继承,因此可能存在多个符合的Bean,容器按照一定的规则选出最符合的Bean。如果BeanDefinitin中有实现了InstantiationAwareBeanPostProcessor接口的BeanPostProcessor,也会遍历执行其postProcessPropertyValues方法。被@Autowired标记的成员变量的值注入就是通过AutowiredAnnotationBeanPostProcessor实现的。可以看出BeanPostProcessor是一个非常强大的插件机制,不光用户可以使用,容器本身也大量应用该机制来优雅的扩展自己的功能。
  3. 对Bean实例执行init-method。用户自定义方法,可以执行在前两个阶段无法做出的初始化动作。
  4. 为Bean实例注册destroy-method。用户自定义方法,容器会在该Bean实例被销毁前执行该方法。

第二态:BeanFactory初始化完成后

在第一态中容器生成了所有非懒加载的单例。
单例(singleton)是容器规定的一种scope(作用域),即Bean在整个容器中保持为唯一。Spring还定义了其他scope:prototype(原型、多例,每次getBean都会重新生成其实例)、request(在WebApplicationContext中有效,只在单个HTTP Request内保持单例)、session(同request,作用于扩大到HTTP Session),用户也可以定义自己的scope。

被定义为懒加载的Bean只有在被用户真正使用到时才会被初始化,在第一态时只是在容器中生成了一个负责真正初始化Bean的代理Bean,用户有2种方式来定义一个懒加载的Bean。第一种通过xml,<bean lazy-init="false" />或者<beans default-lazy-init="true">,前者作用于单个Bean,后者作用于多个Bean;第二种通过注解,如在类上标注@Lazy

在第二态中所有的Bean都只会在被“客户”实际使用到时才会实时生成其实例。

Revison

Reference


Jiadong
454 声望42 粉丝

秋名山撒欢,排水沟过弯