Contents
  1. 1. Spirng
  2. 2. 控制反转
  3. 3. Bean 概述
    1. 3.1. Bean的命名
    2. 3.2. Bean的创建方式
  4. 4. Bean的依赖注入
    1. 4.1. Bean的装配方式
    2. 4.2. Bean的装配类型
    3. 4.3. Bean的装载模式
    4. 4.4. Bean的自动装配
  5. 5. Spring IoC 内幕
    1. 5.1. BeanFactory和ApplicationContext
    2. 5.2. IoC容器初始化
    3. 5.3. 依赖注入

RT,断断续续写……

Spirng

插件:http://spring.io/tools/sts/all

You can use start.spring.io to generate a basic project or follow one of the “Getting Started” guides

日志:http://blog.csdn.net/yycdaizi/article/details/8276265

控制反转

任何一个有实际意义的应用都是由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。如果相互协作的对象(即它所依赖的对象)的引用要由每个对象自己负责管理,将会导致高度耦合难以测试的代码。

耦合具有两面性,一方面紧密耦合的代码难以测试、复用和理解,另一方面一定程度的耦合又是必须的。

如果从具体对象手中交出控制,即把对象的依赖关系交给负责协调系统各个对象的第三方组件管理,就可以解耦代码、提高可测试性——这就是控制反转(Inversion of Control ,IoC)。控制反转主要有两种方式来实现:一是依赖查找(Dependency Lookup或Service Locator),二是依赖注入(dependency injection,DI),将对象自己去“取”依赖的职责反转为框架或IoC容器主动向对象“给”依赖。在Spring中,使用的是依赖注入,即在创建对象时设定依赖,注入到它们的对象中去,具体指的是对象实例通过构造器参数、工厂方法参数、(由构造器或工厂方法得到对象后的)属性设置来获得依赖对象的过程。

通过下例来感受几种管理依赖关系的方式:
(1)原始的,Human的构造器中自行创建了Eyes接口的一种实现BigBlueEyes,这使得Human与BigBlueEyes紧耦合在了一起,极大地限制了Human。更为糟糕的是难以对Human编写单元测试,当Human的see()方法被调用时,无法测试BigBlueEyes的see()方法是否被调用。

1
2
3
4
5
6
7
8
9
public class Human implements Life{
private Eyes eyes;
public Human(){
eyes = new BigBlueEyes();
}
public void see(){
eyes.see();
}
}

(2)通过依赖查找,Human一定程度上解耦了,然而与BigBlueEyes的关系过于隐式,需要通过文字来查找,而且依然不方便测试。此处化简了JNDI代码。

1
2
3
4
5
6
7
8
9
public class Human implements Life{
private Eyes eyes;
public Human(){
eyes = new ServiceLocator().getService("BigBlueEyes");
}
public void see(){
eyes.see();
}
}

(3)用构造器注入的形式(依赖注入的方式还有 Interface注入和setter注入),我们在构造Human时才把Eyes作为构造器参数传入,Human也没有与任何特定的Eyes实现发生耦合,这个Human长了什么样的Eyes,要等到Eyes的某种实现被注入时才知道。通过依赖注入和面向接口,Human与Eyes变成松耦合的关系。在测试时,用mock对象框架可以方便地进行单元测试,验证Eyes的eye()方法是否只被调用了一次。

1
2
3
4
5
6
7
8
9
public class Human implements Life{
private Eyes eyes;
public Human(Eyes eyes){
this.eyes = eyes;
}
public void see(){
eyes.see();
}
}

——参考自Inversion of Control Containers and the Dependency Injection pattern这篇


Bean 概述

在Spring中,由Spring IoC容器管理的对象称为Bean。容器通过XML、Java annotations或者Java code形式的configuration metadata来获得bean和它们之间的关系。

以下都先以XML为例。先做一个简单的示例。

在Maven的pom.xml中加入对log4j(log可以给我们提供很多的信息)和Spring的依赖。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

添加x/y/beans.xml,默认bean的singleton-scoped(一个容器只维护一个单例)和pre-instantiated(ApplicationContext容器初始化启动后立刻创建配置bean,并完成依赖注入)。

1
2
3
4
5
6
7
<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-4.2.xsd">


<bean id="foo" class="x.y.Foo"/>

</beans>

1
2
3
package x.y;
public class Foo{
}
1
2
3
4
5
BasicConfigurator.configure();	//配置log4j
Logger.getRootLogger().setLevel(Level.ALL); //最低级别,输出所有日志

ApplicationContext ctx=new ClassPathXmlApplicationContext("x/y/beans.xml"); //应用上下文容器
Foo foo= ctx.getBean("foo", Foo.class);

执行程序,正常,还可以看到一行行的log输出。

Bean的命名

Bean的创建方式

(1) 构造器
这是最普遍的一种方式。

1
<bean id="foo" class="x.y.Foo"/>

1
2
3
public class Foo {
public Foo() {}
}

(2)静态工厂方法
有时候静态工厂方法是实例化对象的唯一方法。

1
<bean id="bar" class="x.y.Foo" factory-method="createBar"/>

1
2
3
4
5
public class Foo {
public static Bar createBar() {
return new Bar();
}
}

更常见的是GoF中的单例模式。

1
<bean id="foo" class="x.y.Foo" factory-method="createInstance"/>

1
2
3
4
5
6
7
public class Foo {
private static Foo foo = new Foo();
private Foo() {}
public static Foo createInstance() {
return foo;
}
}

(3)非静态工厂方法
容器通过一个现有已实例化的bean的非静态工厂方法创建一系列的产品,类似于抽象工厂方法设计模式。

1
2
3
<bean id="foo" class="x.y.Foo"/>
<bean id="bar" factory-bean="foo" factory-method="createBar"/>
<bean id="baz" factory-bean="foo" factory-method="createBaz"/>

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
private static Bar bar = new BarImpl();
private static Baz baz = new BazImpl();
private Foo() {}
public Bar createBar() {
return bar;
}
public Baz createBaz() {
return baz;
}
}

注意区别,class + factory-method 是用class的静态factory-method创建bean,而 factory-bean + factory-method 是用factory-bean的非静态factory-method创建bean,创建的bean都不一定与class、factory-bean同类。

在Spring文档中,通过静态或非静态工厂方法创建对象,由IoC容器配置管理的bean称为factory bean。区别于Spring中的FactoryBean

FactoryBean 专指实现了FactoryBean接口的bean,是一个能够产生或者修饰对象生成的工厂bean。通过定制自己的FactoryBean,可以用Java code代替冗长的XML更好地表示bean复杂的初始化过程。当调用应用上下文的getBean(…)方法时实际得到的是FactoryBean的产品,即调用了bean实现的FactoryBean接口提供的getObject(…)方法。IoC容器的初始化逻辑本身支持FactoryBean的插入,FactoryBean接口对于Spring框架来说有重要的地位,Spring自身就提供了50多个FactoryBean的实现,它们隐藏了一些复杂bean的实例化细节,给上层应用带来便利。

Bean的依赖注入

Bean的装配方式

(1)基于创建的依赖注入
用< constructor-arg >元素表示。在很多地方都叫做 Constructor-based dependency injection,未免令人容易误会,因为不只构造器,创建方式中的静态和非静态工厂方法当然也可以进行依赖注入。

1
2
3
4
5
6
7
8
9
<bean id="bar" class="x.y.Bar">		<!--构造器-->
<constructor-arg value="15"/>
<constructor-arg>
<bean class="x.y.Baz"/> <!--内部bean-->
</constructor-arg>
</bean>
<bean id="foo" class="x.y.Foo" factory-method="createInstance"> <!--工厂-->
<constructor-arg ref="bar"/> <!--引用bean-->
</bean>

1
2
3
public Bar{
public Bar(int num, Baz baz) {}
}
1
2
3
4
5
public class Foo {
public static Foo createInstance(Bar bar) {
//...
}
}

Bar的构造参数num和baz在XML中默认按顺序匹配对应。另外还可以利用< constructor-arg >元素的type、index、name等属性显示地去匹配构造器、工厂方法所需的参数。

(2)基于setter的依赖注入
用< property >元素表示。在调用构造器或静态工厂方法实例化bean后,容器使用setter方法完成依赖注入。

1
2
3
4
5
6
<bean id="foo" class="x.y.Foo">
<property name="num" value="15"/>
<property name="bar">
<bean class="x.y.Bar"/>
</property>
</bean>

1
2
3
4
5
6
7
8
9
10
public class Foo{
private int num;
private Bar bar;
public void setNum(int num){
this.num=num;
}
public void setBar(Bar bar){
this.bar=bar;
}
}

一般,构造器用于注入必须的依赖,setter用于注入可选的依赖。另外,对setter方法声明@Required可令属性成为必选的。

Bean的装配类型

  • 简单值:由< property > and < constructor-arg >的value属性指定,再从文字String转换成bean中真正对应的类型。 还可以用内嵌的< value >元素替换value属性。有时候还可以作为java.util.Properties使用,与后面的集合< props >元素是等价的。

    1
    2
    3
    4
    5
    6
    <property name="properties">
    <value>
    jdbc.driver.className=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/mydb
    </value>
    </property>
  • < idref >元素:获得bean的id值(一个字符串),而不是bean的实例。bean属性指定引用的bean的id,比直接用value属性的方式,容器会在部署时验证所引用的bean是否存在。注:< bean >元素的name属性可被视为bean的别称id,广义上也是id。

  • 引用bean,即< ref >元素:< property > and < constructor-arg >的ref属性实际是内部< ref bean=”…” />的简写,bean属性指定引用的bean的id。
  • 内部bean,即< bean >元素:容器自动忽视id、name和scope等属性,随着外部bean产生。
  • 集合

    • < array >:对应数组
    • < list >:对应List
    • < set >:对应Set
    • < map >:对应Map,名称和值都可是任意类型。< enrty >元素的key属性和value属性是字符串形式,key-ref属性是子元素< key >的子元素 < ref bean=”…” /> 的简写,value-ref属性是子元素 < ref bean=”…” /> 的简写(就是key所对应的value了,不要再写< value >元素了),这样对于只由字符串或bean组成的map,只要一行< entry >就可以写好了。
    • < props >:对应Properties,名称和值都必须是String类型。
      1
      2
      3
      4
      5
      6
      <property name="properties">
      <props>
      <prop key=" jdbc.driver.className">com.mysql.jdbc.Driver</prop>
      <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
      </props>
      </property>
  • 空值,< null >元素对应null关键字,类似setText(null),而value=””实际是setText(“”)。

更多可参看XML的Elment和Content Model给出的说明。

奇淫巧技:

引入c-命名空间xmlns:p="http://www.springframework.org/schema/c"后,简化< constructor-arg >元素。引入p-命名空间xmlns:p="http://www.springframework.org/schema/p"后,简化< property >元素。

1
2
3
4
5
6
7
8
9
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>

<bean id="foo" class="x.y.Foo">
<constructor-arg name="first" value="123"/>
<constructor-arg name="myBar" ref="bar"/>
<property name="second" value="456"/>
<property name="myBaz" ref="baz"/>
</bean>

1
2
3
<bean id="foo" class="x.y.Foo" 
p:first="123" c:myBar-ref="bar"
p:second="456" p:myBaz-ref="baz"/>

除了可以对应参数或者属性的name,也可以按序等。

Bean的装载模式

默认情况下,ApplictaionContext容器(BeanFactory总是懒汉)动就会创建、配置所有的singleton型bean,这就是pre-instantiation模式(饿汉),有助于尽早发现配置或环境的问题。另一种相反的模式是lazy-initialization(懒汉),在向容器索取bean时对bean进行创建。装载模式在XML中由< bean >元素的lazy-init属性控制,若为true则是懒汉模式。

1
<bean id="foo" class="x.y.Foo" lazy-init="true"/>

如果某个饿汉对懒汉有依赖关系,懒汉也就不能懒了,必须创建、配置后被注入饿汉bean。

容器级别(应该是配置文件级别,因为容器可以有多个配置文件?)的装载模式由< beans >元素的< default-lazy-init >属性控制。

Bean的自动装配

引用bean时,总是要去找对应的id,而且重复写大量的相似代码,好累。为bean再增加依赖时,还要给XML再增加代码,好累。看看我上面的代码,似乎总习惯把bean名称和要注入的属性名称取同一个,那容器能不能聪明点,就按这种习惯帮我装配?这就是自动装配的一种,byName,按名称装配。

自动装配(aotowiring)就是Spring自动识别并装配Bean的依赖关系。分为4种类型,可通过< bean >元素的autowire属性指定:

  • no:默认,不自动装配。
  • byName:根据属性名称(或ID)自动装配,减少< property >元素,基于setter方法的依赖注入。如有属性名称为bar,则自动寻找名为bar的bean,若有则调用setBar(Bar)方法进行注入。
  • byType:根据属性类型自动装配,减少< property >元素,基于setter方法的依赖注入。如有属性类型为Bar,则自动寻找类型为Bar的bean,若有则调用setBar(Bar)方法进行注入。
  • constructor:根据属性类型装配,减少< constructor-arg >元素,基于构造器的依赖注入。如有构造器的参数类型为Bar和Baz,则自动寻找类型为Bar和Baz的bean,若有则调用构造器进行注入。

怎么决定用哪种装配类型? < beans >元素的default-autowire属性可设置全局,< bean >元素的autowire属性可以覆盖全局,< bean >元素的子元素< constructor-arg >或< property >又可以再覆盖bean上的作用。即XML一般以内为准。如下所示,如果foo以全局byType找,就会报错,如果以< bean >上的byName找,就应是名为’bar’的bean,而实际上,< property >明确指出foo需要的是’goodBar’的bean。

1
2
3
4
5
6
7
8
9
10
11
12
<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-4.2.xsd"

default-autowire="byType">

<bean id="foo" class="x.y.Foo" autowire="byName">
<property name="bar" ref="goodBar"/>
</bean>
<bean id="goodBar" class="x.y.Foo"/>
<bean id="bar" class="x.y.Foo"/>
</beans>

匹配到的bean不是唯一的? 除了已举例说明的恰好找到唯一的完美情况,当然还有:

  • 零:如果byName和byType找不到对应的属性名称或类型的bean,则不会进行注入,就像< property >常用于可选的属性。而如果constructor找不到对应的构造器参数类型的bean,则会提示错误,< constructor-arg >常用于必选的属性。
  • 多:重复的bean名称一般IDE就先不会允许了,在初始化容器时也会早早报错,因此byName无需苦恼。但是byType和constructor这些根据类型装配的模式,在面对多种同类型的bean时就会报错,如果可以设置其中只有一个bean允许被自动装配,其他不能,那就没有问题了。

怎么设置bean允许被自动装配?

  • < bean >元素的primary属性,起的是突出某一个bean的作用。默认所有候选bean的primary为false(第3版的《Spring技术内幕》时是true,应该是Spring早期版本的失误),如果恰好只有一个候选bean的primary为true,则表示突出的,被选出装配。
  • < bean >元素的autowire-candidate属性,用于设置bean是否允许被自动装配。默认所有候选bean的autowire-candidate为true,表示允许被自动装配。比起primary它有更大的灵活性。(including annotation style configurations such as @Autowired). 顶层的< beans >元素还有个default-autowire-candidates属性,通过对bean的名称进行模式匹配筛选并设置哪些bean允许被自动装配。例如default-autowire-candidates=”*Dao,*Service”表示所有以Dao或Service名称结尾的bean允许被自动装配,其他bean被排除。依然是内部为王, < bean >元素的autowire-candidate属性覆盖全局的default-autowire-candidates属性。

注意区别,禁止“被”自动装配的bean表示不能被自动注入到别的bean中,不表示不可以自动装配别人,被动由上面这几个属性决定。主动是由autowire属性决定的。

自动装配的利与弊

  • 自动装配不仅减少了配置< constructor-arg >或< property >元素,也有利于对象的版本升级。
  • 但是显然,简单属性例如基本类型、java.lang.Class、java.lang.String类、 基本类型数组等无法自动装配。让Spring干太多猜测超出XML文档的事情,容易失控。匹配到多个bean的事情也时常发生。因此自动装配应当慎用,或者考虑用Annotation-based容器配置控制更多的自动装配细节。

Spring IoC 内幕

Spring框架中IoC容器的基础是.org.springframework.beans和org.springframework.context包,有两个主要的容器系列:一个是实现BeanFactory接口的简单容器系列,它们提供了一种能够管理任何类型对象的配置机制;另一个是实现 BeanFactory 的子接口ApplicationContext应用上下文的高级容器系列,它们增加了Spring AOP特性简单集成、消息资源处理(国际化)、事件发布和应用层具体上下文如用于Web应用的WebApplicationContext。

在Spring中,由Spring IoC容器管理的对象称为Bean。在最早的版本中,Spring被设计用来管理JavaBean,而现在Spring已经可以管理任何对象,即使它不具备默认构造器或getter和setter方法这些JavaBean的特性,但“Spring bean”这个术语仍然被保存了下来。Spring bean可以说POJO,甚至还可以是EJB对象等。

JavaBean是符合一定规范编写的Java类,不是一种技术,而是一种规范。JavaBean规范包括:

  1. 所有属性为private
  2. 提供公共的无参构造函数
  3. 提供getter和setter
  4. 实现serializable接口

Sun最早于1996年制定了JavaBean 标准的类,最初的想法是将JavaBean打造成能够被构建工具可视化地操作的可重用的组件。

POJO是Plain Old Java Object的缩写,是除了Java语言规范外没有任何限制的Java对象。POJO不应继承任何外部预先设定的类,不应实现任何外部预先设定的接口,不应包含任何外部预先设定的注释(虽然实际上很多声称纯POJO的软件产品和框架仍然需要使用预先设定的注释,如用作持久层。因此如果移除注释后是POJO的话,则该对象就是POJO)。

早期版本的很多框架都是重量型框架,强迫应用继承它们的类或实现它们的接口,从而让应用跟框架绑死,难以编写测试代码,如EJB 2的无状态会话bean必须实现SessionBean接口。后来发展了很多轻量型框架,不强迫业务对象遵循平台特定接口,允许开发者在POJO中实现业务逻辑,使应用不依附于某种框架,虽然相对于重量级框架,轻量型框架的处理能力也有所下降。

其实JavaBean就是一个serializable、有无参构造函数的、允许用getter和setter方法访问属性的POJO。而POJO就是简单的JavaBean。如果严格遵循JavaBean规范,类必须实现serializable接口,就会略微打破POJO模型。

——参考自WikiImportNew

BeanFactory和ApplicationContext

前面介绍过,BeanFactory接口是容器始祖,DefaultListableBeanFactory是它的一个实现类,现在我们以下面的3行代码的基本容器替代上面示例ApplicationContext ctx=new ClassPathXmlApplicationContext("x/y/beans.xml")这行代码。

1
2
3
DefaultListableBeanFactory ctx=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(ctx);
reader.loadBeanDefinitions("x/y/beans.xml");

程序依然正常。但是log确实不一样了,会发现BeanFactory的确比ApplicationContext简陋。来看看ApplicationContext在源码上都多了些啥功能。在ClassPathXmlApplicationContext的构造器中,实际调用了其高祖(即爹的爹的爹的爹)AbstractApplicationContext抽象类的refresh()方法,ApplicationContext的多功能基本都体现在这了。

2015-09-03_194229.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// 设置允许BeanFactory的(创建)后处理器
postProcessBeanFactory(beanFactory);

// 调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的
invokeBeanFactoryPostProcessors(beanFactory);

//注册Bean的后处理器,在Bean的创建过程中调用
registerBeanPostProcessors(beanFactory);

// 初始化上下文的消息源
initMessageSource();

// 初始化上下文的事件机制
initApplicationEventMulticaster();

// 初始化其他特殊Bean
onRefresh();

// 检查并注册监听器Bean
registerListeners();

// 实例化所有非lazy-init的singleton型Bean.
finishBeanFactoryInitialization(beanFactory);

// 最后,发布相应容器事件
finishRefresh();
}

catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

// 销毁已生成的singletons型Bean,防止资源占用
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

那3行代码只是做了和obtainFreshBeanFactory()方法同样的工作。

1
2
3
4
5
6
7
8
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

其中refreshBeanFactory()方法是抽象方法,真正的实现由子类AbstractRefreshableApplicationContext来完成(模板方法的设计模式)同样,DefaultListableBeanFactory beanFactory = createBeanFactory()先创建容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

loadBeanDefinitions(beanFactory)也是抽象方法,真正的实现由子孙AbstractXmlApplicationContext完成,代码类似,至此可以确定,基于BeanFactory的那3行代码的工作被包含在了ApplicationContext的obtainFreshBeanFactory()方法中完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

IoC容器初始化

从上面基于BeanFactory和基于ApplicationContext的两种方式看出,loadBeanDefinitions(...)的重载方法们作为主力承担了IoC容器的狭义初始化的任务。其中的主要工作,由XmlBeanDefinitionReader的doLoadBeanDefinitions(...)方法进行体现:

1
2
Document doc = doLoadDocument(inputSource, resource);	//读入并解析XML文档
return registerBeanDefinitions(doc, resource); //将XML中定义好的bean转换成BeanDefinition,向容器注册

继续深入,发现交由BeanDefinitionDocumentReader接口的实现类DefaultBeanDefinitionDocumentReader来负责解析和注册bean。

1
2
3
4
5
6
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

DefaultBeanDefinitionDocumentReader类处理多种元素,其中< bean >元素由processBeanDefinition(...)方法负责。最终,由BeanDefinitionParserDelegate代理类的parseBeanDefinitionElement(...)实现解析,DefaultListableBeanFactory类的registerBeanDefinition(...)方法实现注册。详细可看《Spring技术内幕》一书。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 把< bean >解析成相应的BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 向容器注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

总结起来,IoC容器的狭义初始化工作内容包括:

  1. 对configuration metadata进行资源定位、载入
  2. 把用户定义好的bean解析成容器内部的数据结构,即BeanDefinition
  3. 向容器注册这些BeanDefinition

BeanFactory和ApplicationContext容器的初始化都视为广义初始化,但实际后者包含了前者的工作,即狭义初始化部分。

BeanDefinition中包含了关于bean的创建方式、依赖注入方式、装配类型、装配模式、范围scope等信息。以后,容器就可以通过BeanDefinition来创建、注入和管理Bean。

依赖注入

IoC容器的初始化过程不包括依赖注入。依赖注入通常发生在应用第一次通过getBean(...)向容器索取Bean的时候。
在ApplicationContext应用上下文容器中,finishBeanFactoryInitialization(beanFactory)会预实例化(pre-instantiation)所有no-lazy-init的singleton型Bean,而no-lazy-init的singleton型也是Bean的默认形态,预实例化会完成依赖注入。虽然这种容器的使用方式会对容器一开始的性能有一些影响,但却能够提高应用第一次取得Bean的性能,因为依赖注入早已完成。通过< bean >元素的属性lazy-init=”true”,开发者显示地向容器索取Bean时才进行实例化和依赖注入。BeanFactory容器则完全无视lazy-init属性设置,永远是懒汉模式。

Contents
  1. 1. Spirng
  2. 2. 控制反转
  3. 3. Bean 概述
    1. 3.1. Bean的命名
    2. 3.2. Bean的创建方式
  4. 4. Bean的依赖注入
    1. 4.1. Bean的装配方式
    2. 4.2. Bean的装配类型
    3. 4.3. Bean的装载模式
    4. 4.4. Bean的自动装配
  5. 5. Spring IoC 内幕
    1. 5.1. BeanFactory和ApplicationContext
    2. 5.2. IoC容器初始化
    3. 5.3. 依赖注入