IoC就是交易
IoC
(Invert Of Control
,控制反转)的概念其实出现的蛮早的,但是由于概念枯燥难懂所以被接受的程度一直不高,老头子灵机一动,为亲儿子Spring重新起了一个名字DI
(Dependency Inject
,依赖注入)后,总算有人请他出山了。
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步,我把重要的用粗体标出来:
-
obtainFreshBeanFactory
获得新的BeanFactory -
prepareBeanFactory
一些配置 -
invokeBeanFactoryPostProcessors
处理BeanFactory
的回调 -
registerBeanPostProcessors
注册Bean的回调 -
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 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解析加载的过程也分为两步:
- 首先解析xml中默认的命名空间下的xml标签。一共有4种:
<bean>
、<beans>
、<import>
、<alias>
- 接着解析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步:
- Bean的实例化。Java中常见的实例化方法有
new ClassA()
、new ClassA(args...)
,Spring中的分类略有不同,除了上面的有参构造器和无参构造器,还引入了工厂模式的FactoryBean,即用FactoryBean工厂生成产品Bean的实例。还记得上面名为shopInfoOpenService
的Bean吗,这就是FactoryBean的典型使用方式。容器会按照FactoryBean、有参构造器、无参构造器的优先顺序尝试生成Bean实例。 - 为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是一个非常强大的插件机制,不光用户可以使用,容器本身也大量应用该机制来优雅的扩展自己的功能。 - 对Bean实例执行init-method。用户自定义方法,可以执行在前两个阶段无法做出的初始化动作。
- 为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都只会在被“客户”实际使用到时才会实时生成其实例。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。