Spring Bean

Spring Bean

第一节 概述

1.1 Spring Bean是什么?

在Spring中,用来构成应用程序主干并由Spring IoC容器管理的对象就是bean,所以Spring Bean是一个由Spring IoC容器实例化、组装和管理的对象

1.2 概念

  • IoC:IoC(Inversion of Control)即控制反转,通过依赖注入(DI)的方式动态生成依赖对象并注入被依赖对象中,动态的绑定二者。
  • Bean容器:即Spring IoC容器,管理对象和依赖,以及依赖的注入。
  • Bean:Java对象,遵循Bean规范,由Bean容器生成。
  • Bean规范:满足几个条件,包括所有属性为private,提供默认构造方法,提供getter和setter,实现serializable接口。

1.3 BeanFactory和ApplicationContext

Spring自带了多个容器实现,可以归为两种不同的类型:

  • BeanFactorybean工厂是最简单的容器,提供基本的DI支持。
  • ApplicationContext应用上下文基于 BeanFactory 构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。

1.3.1 BeanFactory

org.springframework.beans.factory.BeanFactory 是Spring的Bean容器的一个非常基本的接口,位于spring-beans模块。它包括了各种 getBean 方法,如通过名称、类型、参数等,试图从Bean容器中返回一个Bean实例。还包括诸如 containsBean , isSingleton , isPrototype 等方法判断Bean容器中是否存在某个Bean或是判断Bean是否为单例/原型等等。

  • ListableBeanFactory:在BeanFactory基础上,支持对Bean容器中Bean的枚举。
  • HierarchicalBeanFactory -> ConfigurableBeanFactoryHierarchicalBeanFactory 有个getParentBeanFactory 方法,使得Bean容器具有亲缘关系。而 ConfigurableBeanFactory 则是对BeanFactory的一系列配置功能提供了支持。
  • AutowireCapableBeanFactory:提供了一系列用于自动装配Bean的方法,用户代码比较少用到,更多是作为Spring内部使用。

虽然我们可以在bean工厂和应用上下文之间任选一种,但bean工厂对大多数应用来说往往太低级了,因此,应用上下文要比bean工厂更受欢迎。

1.3.2 ApplicationContext

org.springframework.context.ApplicationContext 是Spring上下文的底层接口,位于 spring-context 模块。它可以视作是 Spring IoC 容器的一种高级形式,也是我们用Spring企业开发时必然会用到的接口,它含有许多面向框架的特性,也对应用环境作了适配。

我们可以看到 ApplicationContext 作为 BeanFactory 的子接口,与 BeanFactory 之间也是通过 HierarchicalBeanFactoryListableBeanFactory 桥接的。ApplicationContext 接口,继承了 MessageSource , ResourceLoader , ApplicationEventPublisher 接口,以 BeanFactory 为主线添加了许多高级容器特性。

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
  • ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
  • FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
  • XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
1
2
3
4
5
6
// 从系统文件中加载上下文
ApplicationContext context = new FileSystemXmlApplicationContext("C:/a.xml");
// 从应用类路径加载上下文
ApplicationContext context = new ClassPathXmlApplicationContext("a.xml");
// 从Java配置中加载上下文
ApplicationContext context = new AnnotationConfigApplicationContext(com.xxx.xxx...Config.class);

当应用上下文准备就绪后就可以调用 context.getBean() 从容器中获取bean。


第二节 生命周期

在传统的Java应用中,bean的生命周期很简单:使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。

相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。Spring Bean的生命周期的执行过程如下:

Spring Bean的生命周期

详细描述:

  1. Spring实例化Bean对象,默认为单例。
  2. Spring将值和Bean的引用注入到对应的对象属性中,即进行依赖注入。
  3. 如果bean实现了 BeanNameAware 接口,Spring将Bean的ID传递给 setBeanName() 方法。
  4. 如果bean实现了 BeanFactoryAware 接口,Spring将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入。
  5. 如果bean实现了 ApplicationContextAware 接口,Spring将调用 setApplicationContext() 方法,将bean所在的应用上下文引用传入。
  6. 如果bean实现了 BeanPostProcessor 接口,Spring将调用 postProcessBeforeInitialization() 方法,即前置处理。
  7. 如果bean实现了 InitializingBean 接口,Spring将调用 afterPropertiesSet() 方法。
  8. 如果bean配置了自定义的 init-method ,若声明则调用对应方法。
  9. 如果bean实现了 BeanPostProcessor 接口,Spring将调用 postProcessAfterInitialization() 方法,即后置处理。
  10. 此时Bean已准备就绪可以被应用使用了,将一直驻留在应用上下文中,直到此上下文被销毁。
  11. 如果bean实现了 DisposableBean 接口,Spring将调用 destroy() 方法。
  12. 如果bean配置了自定义 destroy-method ,则会调用对应销毁方法。

2.1 实例化Bean

  • 对于 BeanFactory 容器,当用户向容器请求一个尚未初始化的Bean时,或者是初始化Bean的时候需要注入另一个尚未初始化的依赖时,容器会调用 createBean() 进行实例化。

  • 对于 ApplicationContext 容器,容器启动后就实例化所有Bean。

容器通过获取 BeanDefinition 对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。实例化对象被包装在 BeanWrapper 对象中,BeanWrapper 提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

2.2 依赖注入

设置对象属性,按照Spring上下文对实例化的Bean进行配置。

2.3 BeanNameAware

紧接着,Spring会检测该Bean对象是否实现了 BeanNameAware 接口,如果实现了则Spring将Bean的ID传递给 setBeanName() 方法。

1
2
3
4
// 实现此接口主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的
public interface BeanNameAware extends Aware {
void setBeanName(String var1);
}

2.4 BeanFactoryAware

继续检测Bean是否实现了 BeanFactoryAware 接口,如果实现了,则调用 setBeanFactory() 方法,并传递 BeanFactory 实例。

1
2
3
4
// 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory var1) throws BeansException;
}

2.5 ApplicationContextAware

继续检测Bean是否实现了 ApplicationContextAware 接口,如果实现了,则调用 setApplicationContext() 方法,并传递应用上下文。

1
2
3
4
// 作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext的参数传入,而Spring容器在调用setBeanDactory前需要指定(注入)setBeanDactory里的参数BeanFactory
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext var1) throws BeansException;
}

2.6 BeanPostProcessor

当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过 BeanPostProcessor 接口实现。

1
2
3
4
5
6
7
8
9
10
11
12
// 作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

该接口提供了两个函数:

  • postProcessBeforeInitialization(Object bean, String beanName) :当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。这个函数会先于 InitialzationBean 执行,因此称为前置处理。所有Aware接口的注入就是在这一步完成的。
  • postProcessAfterInitialization(Object bean, String beanName) :当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。这个函数会在 InitialzationBean 完成后执行,因此称为后置处理

2.7 InitializingBean与init-method

BeanPostProcessor 的前置处理完成后就会进入本阶段,调用 afterPropertiesSet() 方法。

1
2
3
4
// 作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}

这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。

若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行 afterPropertiesSet() 函数。当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了 init-method 属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method 本质上仍然使用了 InitializingBean 接口。

2.8 DisposableBean和destroy-method

经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁。

Bean可以实现 DisposableBean 接口,和 init-method 一样,通过给 destroy-method 指定函数,就可以在bean销毁前执行指定的逻辑。

1
2
3
4
// 作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法
public interface DisposableBean {
void destroy() throws Exception;
}

第三节 装配Bean

在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。

创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。

3.1 Spring配置的可选方案

作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  • 在XML中进行显式配置。
  • 在Java中进行显式配置。
  • 隐式的bean发现机制和自动装配。

我们需要根据情况选择最适合的方案,通常建议尽量使用自动化装配、然后是显式的JavaConfig、避免使用XML。

3.2 自动化装配bean

Spring从两个角度来实现自动化装配:

  • 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
  • 自动装配(autowiring):Spring自动满足bean之间的依赖。

我们通过CD播放器和CD的案例来阐述DI依赖注入如何运行。

3.2.1 创建可被发现的bean

CompactDisc接口在Java中定义了CD的概念。

1
2
3
public interface CpmpactDisc {
void play();
}

带有 @Component 注解的CompactDisc实现类SgtPeppers。

1
2
3
4
5
6
7
8
9
@Component // 这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper`s Lonely Hearts Club Band";
private String artist = "The Beatles";

public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}

@ComponentScan 注解启用了组件扫描,组件扫描默认是不启用的,默认会扫描与配置类相同的包,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean 。

1
2
3
4
@Configuration
@ComponentScan
public class CDPlayerConfig {
}

如果你更倾向于使用XML来启用组件扫描的话,那么可以使用 Spring context 命名空间的 <context:component-scan> 元素。

接下来就可以通过JUnit来测试我们创建的两个类,判断CompactDisc是否被创建出来。

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}

3.2.2 为组件扫描的bean命名

Spring应用上下文中所有的bean都会给定一个ID。尽管我们没有明确地为SgtPeppers bean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。 如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给 @Component 注解,或使用Java依赖注入规范(Java Dependency Injection)中所提供的 @Named 注解来为bean设置ID。

1
2
3
4
5
// @Named("lonelyHeartsClub")
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}

3.2.3 设置组件扫描的基础包

我们没有为 @ComponentScan 设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package)来扫描组件。但是,如果你想扫描不同的包,那该怎么办呢?或者,如果你想扫描多个基础包,那又该怎么办呢?

如果我们想要将配置类放在单独的包中,使其与其他的应用代码区分开来,那默认的基础包就不能满足要求了。为了指定不同的基础包,你所需要做的就是在 @ComponentScan 的value属性中指明包的名称:

1
2
3
4
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}

如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:

1
2
3
4
5
@Configuration
@ComponentScan(basePackages="soundsystem")
// 复数数组:@ComponentScan(basePackages={"soundsystem","video"})
public class CDPlayerConfig {
}

在上面的例子中,所设置的基础包是以String类型表示的。我认为这是可以的,但这种方法是类型不安全(not type-safe)的。如果你重构代码的话,那么所指定的基础包可能就会出现错误了。 除了将包设置为简单的String类型之外,@ComponentScan 还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:

1
2
3
4
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}

这些类所在的包将会作为组件扫描的基础包。 尽管在样例中,我为 basePackageClasses 设置的是组件类,但是你可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface)。通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码(在稍后重构中,这些应用代码有可能会从想要扫描的包中移除掉)。

在你的应用程序中,如果所有的对象都是独立的,彼此之间没有任何依赖,就像 SgtPeppersbean这样,那么你所需要的可能就是组件扫描而已。但是,很多对象会依赖其他的对象才能完成任务。这样的话,我们就需要有一种方法能够将组件扫描得到的bean和它们的依赖装配在一起。要完成这项任务,我们需要了解一下Spring自动化配置的另外一方面内容,那就是自动装配。

3.2.4 通过为bean添加注解实现自动装配

简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的 @Autowired 注解。

比方说如下代码中的CDPlayer类。它的构造器上添加了 @Autowired 注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过自动装配,将一个CompactDisc注入到CDPlayer之中
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;

@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

public void play() {
cd.play();
}
}

@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果 CDPlayer有一个 setCompactDisc() 方法,那么可以采用如下的注解形式进行自动装配:

1
2
3
4
@Autowired
public void setCompactDisc(CompactDisc cd) {
this.cd = cd;
}

实际上,Setter方法并没有什么特殊之处。@Autowired 注解可以用在类的任何方法上。假 设CDPlayer类有一个 insertDisc() 方法,那么@Autowired能够像在 setCompactDisc() 上那样,发挥完全相同的作用。

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。 假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。 如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将 @Autowired 的required属性设置为false:

1
2
3
4
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

将required属性设置为false时,Spring仍会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现 NullPointerException 。如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配(自动装配中的歧义性)。

@Autowired 是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为 @Inject

1
2
3
4
5
6
7
@Named
public class CDPlayer {
@Inject
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
}

@Inject 注解来源于Java依赖注入规范,该规范同时还为我们定义了 @Named 注解。在自动 装配中,Spring同时支持 @Inject@Autowired 。尽管 @Inject@Autowired 之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。

3.2.5 验证自动装配

现在,我们已经在CDPlayer的构造器中添加了@Autowired注解,Spring将把一个可分配给CompactDisc类型的bean自动注入进来。为了验证这一点,让我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired
private MediaPlayer player;
@Autowired
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}

@Test
public void play() {
player.paly();
assertEquals(
"Playing Sgt. Pepper`s Lonely Hearts Club Band" +
" by The Beatles\n",
log.getLog());
}
}

现在,除了注入CompactDisc,我们还将CDPlayer bean注入到测试代码的player成员变量之中(它是更为通用的MediaPlayer类型)。在 play() 测试方法中,我们可以调用CDPlayer的 play() 方法,并断言它的行为与你的预期一致。 在测试代码中使用 System.out.println() 是稍微有点棘手的事情。因此,该样例中使用了 StandardOutputStreamLog,这是来源于System Rules库的一个JUnit规则,该规则能够基于控制台的输出编写断言。在这里,我们断言 SgtPeppers.play() 方法的输出被发送到了控制台上。

现在我们先将组件扫描和自动装配放在一边,看一下在Spring中如何显式地装配bean,首先从通过Java代码编写配置开始。

3.3 通过Java代码装配bean

尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加 @Component@Autowired 注解的,因此就不能使用自动化装配的方案了。

在进行显式配置的时候,有两种可选方案: Java和XML。在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。 同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是 配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。

3.3.1 创建配置类

重温一下前文样例中的CDPlayerConfig:

1
2
3
@Configuration
public class CDPlayerConfig {
}

创建JavaConfig类的关键在于为其添加 @Configuration 注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean。尽管我们可以同时使用组件扫描和显式配置,但是在这里,我们更加关注于显式配置,因此将CDPlayerConfig的 @ComponentScan 注解移除掉了。移除了 @ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。如果你现在运行CDPlayerTest的话,测试会失败,并且会出现 BeanCreationException 异常。

测试期望被注入CDPlayer和CompactDisc,但是这些bean根本就没有创建,因为组件扫描不会发现它们。 为了再次让测试通过,你可以将 @ComponentScan 注解添加回去,但是我们这里更关注显式配置,因此让我们看一下如何使用JavaConfig装配CDPlayer和CompactDisc。

3.3.2 声明简单的bean

要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加 @Bean 注解。

1
2
3
4
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}

@Bean 注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。

默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是 sgtPeppers 。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:

1
2
3
4
@Bean(name = "xx")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}

方法体返回了一个新的SgtPeppers实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功 能,只要最终生成一个CompactDisc实例即可。 比如我们可以在一组CD中随机选取一个CompactDisc来播放。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
public CompactDisc randomBeatlesCD() {
int choice = (int) Math.floor(Math.random() * 4);
if(choice == 0){
return new SgtPeppers();
} else if(choice == 1){
return new WhiteAlbum();
} else if(choice == 2){
return new HardDaysNight();
} else{
return new Resolver();
}
}

3.3.3 借助JavaConfig实现注入

我们前面所声明的CompactDisc bean是非常简单的,它自身没有其他的依赖。但现在,我们需要声明CDPlayer bean,它依赖于CompactDisc。在JavaConfig中,要如何将它们装配在一起呢?

在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。

1
2
3
4
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}

cdPlayer() 方法像 sgtPeppers() 方法一样,同样使用了 @Bean 注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。

看起来,CompactDisc是通过调用 sgtPeppers() 得到的,但情况并非完全如此。因为 sgtPeppers() 方法上添加了 @Bean 注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。

比如引入了一个其他的CDPlayerbean,它和之前的那个bean完全一样:

1
2
3
4
@Bean
public CDPlayer anotherCdPlayer() {
return new CDPlayer(sgtPeppers());
}

假如对 sgtPeppers() 的调用就像其他的Java方法调用一样的话,那么每个CDPlayer实例都会有一个自己特有的SgtPeppers实例。D光盘的话,这么做是有意义的。如果你有两台CD播放器,在物理上并没有办法将同一张CD光盘放到两个CD播放器中。 但是,在软件领域中,我们完全可以将同一个SgtPeppers实例注入到任意数量的其他bean之中。

默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对 sgtPeppers() 的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用 sgtPeppers() 时所创建的CompactDisc bean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。

可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

1
2
3
4
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}

在这里,cdPlayer() 方法请求一个CompactDisc作为参数。当Spring调用 cdPlayer() 创建CDPlayer bean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer() 方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的 @Bean 方法。

3.4 通过XML装配bean

在Spring刚刚出现的时候,XML是描述配置的主要方式。在Spring的名义下,我们创建了无数行XML代码,在一定程度上,Spring成为了XML配置的同义词。 希望本部分内容只是用来帮助你维护已有的XML配置,在完成新的Spring工作时,建议使用自动化配置和JavaConfig。

3.4.1 创建XML配置规范

在使用XML为Spring装配bean之前,你需要创建一个新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有 @Configuration 注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以 <beans> 元素为根。

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="....">
<!-- configuration details go here -->
</beans>

用来装配bean的最基本的XML元素包含在spring-beans模式之中,在上面这个XML文件中,它被定义为根命名空间。<beans> 是该模式中的一个元素,它是所有Spring配置文件的根元素。

3.4.2 声明一个简单的 <bean>

<bean> 元素类似于JavaConfig中的@Bean注解:

1
<bean class="xx.SgtPeppers">

这里声明了一个很简单的bean,创建这个bean的类通过class属性来指定的,并且要使用全限定的类名。

因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的ID将会是 “xx.SgtPeppers#0” 。其中,“#0”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个SgtPeppers,并且没有明确进行标识,那么它自动得到的ID将会是 “xx.SgtPeppers#1”

尽管自动化的bean命名方式非常方便,但如果你要稍后引用它的话,那自动产生的名字就没有多大的用处了。因此,通常来讲更好的办法是借助id属性,为每个bean设置一个你自己选择的名字:

1
<bean id="compactDisc" class="xx.SgtPeppers">

声明后我们不再需要直接负责创建SgtPeppers的实例,Spring的XML配置并不能从编译期的类型检查中获益,需要其他工具来帮助检查XML配置的合法性。

3.4.3 借助构造器注入初始化bean

在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

  • <constructor-arg> 元素
  • 使用Spring 3.0所引入的 c- 命名空间

两者的区别在很大程度就是是否冗长烦琐。可以看到,<constructor-arg> 元素比使用 c- 命名空间会更加冗长,从而导致XML更加难以读懂。另外,有些事情 <constructor-arg> 可以做到,但是使用 c- 命名空间却无法实现。

1
2
3
4
5
6
7
<!-- 构造器注入bean引用 -->
<bean id="cdPlayer" class="xx.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
<!-- c-命名 -->
<bean id="cdPlayer" class="xx.CDPlayer"
c:cd-ref="compactDisc">

当Spring遇到这个 <bean> 元素时,它会创建一个CDPlayer实例。<constructor-arg> 元素会告知Spring要将一个ID为 compactDisc 的bean引用传递到CDPlayer的构造器中。

属性名以 c: 开头,也就是命名空间的前缀。cd 就是要装配的构造器参数名(可以改为序号如 _0_ ),在此之后是 -ref ,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”。

1
2
3
4
5
6
7
8
9
<!-- 将字面量注入到构造器中 -->
<bean id="compactDisc" class="xx.BlankDisc">
<constructor-arg value="Sgt. Pepper`s Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
<!-- c-命名 -->
<bean id="compactDisc" class="xx.BlankDisc"
c:_title="Sgt. Pepper`s Lonely Hearts Club Band"
c:_artist="The Beatles">

XML不允许某个元素的多个属性具有相同的名字。

有一种情况是 <constructor-arg> 能够实现,c- 命名空间却无法做到:装配集合

1
2
3
4
5
6
7
8
9
10
<!-- 装配集合 -->
<bean id="compactDisc" class="xx.BlankDisc">
<constructor-arg value="Sgt. Pepper`s Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>XXX</value>
</list>
</constructor-arg>
</bean>

其中,<list> 元素是 <constructor-arg> 的子元素,这表明一个包含值的列表将会传递 到构造器中。其中,<value> 元素用来指定列表中的每个元素。

与之类似,我们也可以使用 <ref> 元素替代 <value>,实现bean引用列表的装配。<list> 元素当然也可以换成 <set> 元素。

3.4.4 设置属性

到目前为止,CDPlayer和BlankDisc类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。

1
2
3
<bean id="cdPlayer" class="xx.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>

<property> 元素为属性的Setter方法所提供的功能与 <constructor-arg> 元素为构造器所提供的功能是一样的。在本例中,它引用了ID为compactDisc的bean(通过 ref 属性),并将其注入到compactDisc属性中(通过 setCompactDisc() 方法)。如果你现在运行测试的话,它应该就能通过了。

Spring提供了更加简洁的 p- 命名空间,作为 <property> 元素的替代方案。

3.5 导入和混合配置

在自动装配时,容器并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。

3.5.1 在JavaConfig中引用XML配置

@Import 注解将配置类组合在一起,@ImportResource 注解引入XML配置。

3.5.2 在XML配置中引用JavaConfig

在XML中,我们可以使用import元素来拆分XML配置。 但 <import> 元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。

但是,有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:<bean> 元素。为了将JavaConfig类导入到XML配置中,我们可以这样声明bean:

1
2
3
4
<bean class="xx.CDConfig" />

<bean id="cdPlayer" class="xx.CDPlayer"
c:cd-ref="compactDisc"/>

或是通过XML将两个配置文件组合在一起:

1
2
3
<bean class="xx.CDConfig" />

<import resource="cdPlayer-config.xml" />

第四节 高级装配

在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。不同的环境很多参数配置等可能都需要变化,如数据库配置、加密算法以及与外部系统的集成等是跨环境部署时会发生变化的几个典型例子。

例如我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置。 其中一种方式就是在单独的配置类(或XML文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中。这种方式的问题在于要为每种环境重新构建应用。当从开发阶段迁移到QA阶段时,重新构建也许算不上什么大问题。但是,从QA阶段迁移到生产阶段时,重新构建可能会引入bug并且会在QA团队的成员中带来不安的情绪。

4.1 环境与profile

Spring为环境相关的bean所提供的解决方案:不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。

4.1.1 配置profile bean

在3.1版本中,Spring引入了 bean profile 的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Profile
public class DevelopmentProfileConfig {
@Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}

@Profile 注解应用在类上,告诉Spring这个配置类中的bean只有在 dev profile 激活时才会创建。如果 dev profile 没有激活的话,那么带有 @Bean 注解的方法都会被忽略掉。

从Spring 3.2开始,也可以在方法级别上使用 @Profile 注解,与 @Bean 注解一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中。

注意:没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。

我们也可以通过 <beans> 元素的profile属性,在XML中配置 profile bean

4.1.2 激活profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:

  • spring.profiles.active
  • spring.profiles.default
  1. 如果设置了 spring.profiles.active 属性的话,那么它的值就会用来确定哪个profile是激活的。
  2. 如果没有设置 spring.profiles.active 属性的话,那Spring将会查找 spring.profiles.default 的值。
  3. 如果 spring.profiles.activespring.profiles.default 均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。

有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数;
  • 作为Web应用的上下文参数;
  • 作为JNDI条目;
  • 作为环境变量;
  • 作为JVM的系统属性;
  • 在集成测试类上,使用 @ActiveProfiles 注解设置。

Spring提供了 @ActiveProfiles 注解,我们可以使用它来指定运行测试时要激活哪个profile。

在条件化创建bean方面,Spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。Spring 4.0中提供了一种更为通用的机制来实现条件化的bean定义,在这种机制之中,条件完全由你来确定。让我们看一下如何使用Spring 4和 @Conditional 注解定义条件化的bean。

4.2 条件化的bean

假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后,才会创建某个bean。

Spring 4引入了一个新的 @Conditional 注解,它可以用到带有 @Bean 注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。

假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略。

1
2
3
4
5
@Bean
@Conditional(MagicExistsCondition.class) // 条件化的创建bean
public MagicBean magicBean() {
return new MagicBean();
}

可以看到,@Conditional中给定了一个Class,它指明了条件——在本例中,也就是 MagicExistsCondition@Conditional 将会通过 Condition 接口进行条件对比:

1
2
3
public interface Condition {
boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata);
}

设置给 @Conditional 的类可以是任意实现了 Condition 接口的类型。可以看出来,这个 接口实现起来很简单直接,只需提供 matches() 方法的实现即可。如果 matches() 方法返 回true,那么就会创建带有 @Conditional 注解的bean。如果 matches() 方法返回 false,将不会创建这些bean。

1
2
3
4
5
6
7
public class MagicExistsCondition implements Condition {

public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata){
Environment env = context.getEnvironment();
return env.containsProperty("magic"); // 检查magic属性
}
}

通过给定的 ConditionContext 对象进而得到 Environment 对象,并使用这个对象检查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么无所谓,只要属性存在即可满足要 求。如果满足这个条件的话,matches() 方法就会返回true。所带来的结果就是条件能够得到满足,所有 @Conditional 注解上引用 MagicExistsCondition 的bean都会被创建。

ConditionContext 是一个接口,大致如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public interface ConditionContext {
// 通过BeanDefinitionRegistry检查bean定义
BeanDefinitionRegistry getRegistry();
// 通过ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
ConfigurableListableBeanFactory getBeanFactory();
// 通过Environment检查环境变量是否存在以及它的值是什么
Environment getEnvironment();
// 通过ResourceLoader读取并检查加载的资源
ResourceLoader getResourceLoader();
// 通过ClassLoader加载并检查类是否存在
ClassLoader getClassLoader();
}

AnnotatedTypeMetadata 则能够让我们检查带有 @Bean 注解的方法上还有什么其他的注 解。

1
2
3
4
5
6
7
8
9
10
11
public interface AnnotatedTypeMetadata {
// 判断带有@Bean注解的方式是不是还有其他特定的注解
boolean isAnnotated(String annotationType);
// 其他方法用来检查@Bean注解的方法上其他注解的属性。
Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(
String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString);
}

非常有意思的是,从Spring 4开始,@Profile 注解进行了重构,使其基于 @ConditionalCondition 实现。作为如何使用 @ConditionalCondition 的例子,我们来看一下在Spring 4中,@Profile 是如何实现的。

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Targer({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ProfileCondition implements Condition {

public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata) {
if(context.getEnvironment() != null){
// 得到用于@Profile注解的所有属性
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if(attrs != null){
// 检查value属性,此属性包含了bean的Profile名称
for(Object value : attrs.get("value")){
// 检查该Profile是否处于激活状态
if(context.getEnvironment()
.acceptsProfiles((String[]) value)))
return true;
}
return false;
}
}
return true;
}
}

4.3 处理自动装配的歧义性

如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。

为了阐述自动装配的歧义性,假设我们使用 @Autowired 注解标注了 setDessert() 方法:

1
2
3
4
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

Dessert是一个接口,并且有三个类实现了这个接口,分别为Cake、Cookies 和IceCream:

1
2
3
4
5
6
@Component
public class Cake implements Dessert { ... }
@Component
public class Cookies implements Dessert { ... }
@Component
public class IceCream implements Dessert { ... }

因为这三个实现均使用了 @Component 注解,在组件扫描的时候,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配 setDessert() 中的 Dessert参数时,它并没有唯一、无歧义的可选值。在从多种甜点中做出选择时,尽管大多数人并不会有什么困难,但是Spring却无法做出选择。Spring此时别无他法,只好宣告失败并抛出异常 NoUniqueBeanDefinitionException

当确实发生歧义性的时候,Spring提供了多种可选方案来解决这样的问题:

  • 你可以将可选bean中的某一个设为首选(primary)的bean。
  • 使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean。

4.3.1 标示首选的bean

在Spring中,可以通过 @Primary 来表达最喜欢的方案。@Primary 能够与 @Component 组合用在组件扫描的bean上,也可以与 @Bean 组合用在Java配置的bean声明中。比如,下面的代码展现了如何将 @Component 注解的IceCream bean声明为首选的bean:

1
2
3
@Component
@Primary
public class IceCream implements Dessert { ... }

或者,如果你通过Java配置显式地声明IceCream,那么 @Bean 方法应该如下所示:

1
2
3
4
5
@Bean
@Primary
public Dessert iceCream() {
return new IceCream();
}

如果你使用XML配置bean的话,同样可以实现这样的功能。<bean> 元素有一个primary属性用来指定首选的bean:

1
<bean id="iceCream" class="xx.IceCream" primary="true" />

如果你标示了两个或更多的首选bean,那么它就无法正常工作了。就解决歧义性问题而言,限定符是一种更为强大的机制

4.3.2 限定自动装配的bean

设置首选bean的局限性在于 @Primary 无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。

与之相反,Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。如果将所有的限定符都用上后依然存在歧义性,那么你可以继续使用更多的限定符来缩小选择范围。

@Qualifier 注解是使用限定符的主要方式。它可以与 @Autowired@Inject 协同使用,在注入的时候指定想要注入进去的是哪个bean。例如,我们想要确保要将IceCream注入到setDessert()之中:

1
2
3
4
5
@Autowired
@Qualifier("iceCream") // 限定符
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

这是使用限定符的最简单的例子。为 @Qualifier 注解所设置的参数就是想要注入的bean的 ID。所有使用 @Component 注解声明的类都会创建为bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier("iceCream") 指向的是组件扫描时所创建的bean,并且这个bean是IceCream类的实例。

@Qualifier("iceCream") 所引用的bean要具有String类型的 “iceCream” 作为限定符。如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,这个限定符与bean的ID相同。因此,框架会将具有 “iceCream” 限定符的bean注入到 setDessert() 方法中。恰巧就是ID为iceCream的bean,它是IceCream类在组件扫描的时候创建的。

如果你重构了IceCream类,将其重命名为Gelato的话,那此时会发生什么情况呢?如果这样的话,bean的ID和默认的限定符会变为gelato,这就无法匹配 setDessert() 方法中的限定符。自动 装配会失败。

这里的问题在于 setDessert() 方法上所指定的限定符与要注入的bean的名称是紧耦合的对类名称的任意改动都会导致限定符失效

(1)创建自定义的限定符

我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。

1
2
3
@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }

在这种情况下,cold限定符分配给了IceCreambean。因为它没有耦合类名,因此你可以随意重构IceCream的类名,而不必担心会破坏自动装配。在注入的地方,只要引用cold限定符就可以了:

1
2
3
4
5
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
(2)使用自定义的限定符注解

面向特性的限定符要比基于bean ID的限定符更好一些。但是,如果多个bean都具备相同特性的话,这种做法也会出现问题。例如,如果引入了这个新的Dessert bean,会发生什么情况呢:

1
2
3
@Component
@Qualifier("cold")
public class Popsicle implements Dessert { ... }

现在我们有了两个带有“cold”限定符的甜点,需要使用更多的限定符来将可选范围限定到只有一个bean。

首先想到方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert { ... }
@Component
@Qualifier("cold")
@Qualifier("fruity")
public class Popsicle implements Dessert { ... }

@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

这里只有一个小问题:Java不允许在同一个条目上重复出现相同类型的多个注解(Java 8允许出现重复的注解,只要这个注解本身在定义的时候带有 @Repeatable 注解就可以。不过,Spring的 @Qualifier 注解并没有在定义时添加 @Repeatable 注解)。不过我们可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。

创建一个新的 @Cold 注解来代替 @Qualifier("cold")

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Targer({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Qualifier
public @interface Cold {
}

创建一个新的 @Creamy 注解来代替 @Qualifier("creamy")

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Targer({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Qualifier
public @interface Creamy {
}

自定义注解的方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Cold
@Creamy
public class IceCream implements Dessert { ... }
@Component
@Cold
@Fruity
public class Popsicle implements Dessert { ... }

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。与此同时,相对于使用原始的 @Qualifier 并借助String类型来指定限定符,自定义的注解也更为类型安全

4.4 bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。

在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。

有时候,可能会发现,你所使用的类是易变的(mutable),它们会保持一些状态,因此重用 是不安全的。在这种情况下,将class声明为单例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题。

Spring的五种作用域如下:

  • Singleton(单例):在整个应用中,只创建bean的一个实例。
  • Prototype(原型):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • Request :每次HTTP请求都会创建一个新的Bean,该作用域仅适用于 WebApplicationContext 环境。
  • Session :同一个 HTTP Session 共享一个Bean,不同Session使用不同Bean,仅适用于 WebApplicationContext 环境。
  • GlobalSession :一般用于 Portlet 应用环境,它映射到 Portlet 的 global 范围内的 session,仅适用于 WebApplicationContext 环境。

选择其他的作用域,要使用 @Scope 注解,它可以与 @Component@Bean 一起使用。

1
2
3
4
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
// 选用原型作用域,也可以这样写:@Scope("prototype")
public class Notepad { ... }

XML中配置bean的作用域如下:

1
2
3
4
<!-- 声明Bean为原型 -->
<bean scope="prototype"/>
<!-- 或者 -->
<bean singleton="false"/>

4.4.1 使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。

就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用 @Scope 注解,它的使用方式与指定原型作用域是相同的:

1
2
3
4
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }

Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。

要注意的是,@Scope 同时还有一个proxyMode属性,它被设置成了 ScopedProxyMode.INTERFACES 。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题(通过动态代理技术生成会话/请求作用域的代理类作为注入对象,等到方法被调用时再委托给真正的bean)。

假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中,如下所示:

1
2
3
4
5
6
7
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
}

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean注入到 setShoppingCart() 方法中。但是ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如图3.1所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

现在,我们带着对这个作用域的理解,讨论一下proxyMode属性。如配置所示,proxyMode属性被设置成了 ScopedProxyMode.INTERFACES ,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。

如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了(JDK动态代理基于接口)。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为 ScopedProxyMode.TARGET_CLASS ,以此来表明要以生成目标类扩展的方式创建代理。当然请求作用域的bean会面临相同的装配问题。

4.4.2 在XML中声明作用域代理

使用XML来声明会话或请求作用域的bean,那么就不能使用 @Scope 注解及其proxyMode属性了。<bean> 元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?

要设置代理模式,我们需要使用 Spring aop 命名空间的一个新元素:

1
2
3
4
5
6
7
<bean id="cart" class="xxx.ShoppingCart" 
scope="session">
<!-- 默认CGLib代理 -->
<aop:scoped-proxy />
<!-- JDK代理 -->
<aop:scoped-proxy proxy-target-class="false" />
</bean>

<aop:scoped-proxy> 是与 @Scope 注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将 proxy-target-class 属性设置为false,进而要求它生成基于接口的代理:

4.5 运行时值注入

当讨论依赖注入的时候,我们通常所讨论的是将一个bean引用注入到另一个bean的属性或构造器参数中。它通常来讲指的是将一个对象与另一个对象进行关联。但是bean装配的另外一个方面指的是将一个值注入到bean的属性或者构造器参数中,实现的时候是将值硬编码在配置类中的。

1
2
3
4
5
6
7
@Bean
public CompactDisc sgtPeppers() {
return new BlankDisc(
"Sgt. Peppers`s Lonely Hearts Club Band",
"The Beatles"
);
}

有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder)。
  • Spring表达式语言(SpEL)。

4.5.1 注入外部的值

在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@PropertySource("classpath:/com/xxx/app.properties") // 声明属性源
public class ExpressiveConfig {
@Autowired
Environment env;

@Bean
public BlankDisc disc() {
return new BlankDisc(
// 检索属性值
env.getProperty("disc.title"),
env.getProperty("disc.artist")
);
}
}

Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用 ${ ... } 包装的属性名称。

1
2
3
<bean id="sgtPeppers" class="xxx.BlankDisc" 
c:_title="${disc.title}"
c:_artist="${disc.artist}" />

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用 @Value 注解,它的使用方式与 @Autowired 注解非常相似。比如,在BlankDisc类中,构造器可以改成如下所示:

1
2
3
4
5
public BlankDisc (@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist){
this.title = title;
this.artist = artist;
}

为了使用占位符,我们必须要配置一个 PropertyPlaceholderConfigurer beanPropertySourcesPlaceholderConfigurer bean 。从Spring 3.1开始,推荐使用 PropertySourcesPlaceholderConfigurer ,因为它能够基于 Spring Environment 及其属性源来解析占位符。

如下的 @Bean 方法在Java中配置了 PropertySourcesPlaceholderConfigurer

1
2
3
4
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

XML通过Spring context命名空间中的 <context:propertyplaceholder> 元素为你生成 PropertySourcesPlaceholderConfigurer bean

1
2
3
<beans xmlns="...">
<context:propertyplaceholder />
</beans>

4.5.2 使用Spring表达式语言进行装配

Spring 3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。

SpEL拥有很多特性,包括:

  • 使用bean的ID来引用bean;
  • 调用方法和访问对象的属性;
  • 对值进行算术、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作。

SpEL能够用在依赖注入以外的其他地方,例如Spring Security支持使用SpEL表达式定义安全限制规则。另外,如果你在Spring MVC应用中使用Thymeleaf模板作为视图的话,那么这些模板可以使用SpEL表达式引用模型数据。

(1)SpEL样例

需要了解的第一件事情就是SpEL表达式要放到 #{ ... } 之中,类似于属性占位符需要放到 ${ ... } 之中。

1
2
3
4
5
6
7
8
// 最简单的SpEL表达式
#{1}
// 计算表达式当前时间,T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。
#{T(System).currentTimeMilis()}
// 引用其他的bean或其他bean的属性
#{sgtPeppers.artist}
// 可以通过systemProperties对象引用系统属性
#{systemProperties[disc.title]}

在bean装配的时候如何使用这些表达式?

如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,这与之前看到的属性占位符非常类似。不过,在这里我们所使用的不是占位符表达式, 而是SpEL表达式。

1
2
3
4
5
public BlankDisc (@Value("#{systemProperties[disc.title]}") String title,
@Value("#{systemProperties[disc.artist]}") String artist){
this.title = title;
this.artist = artist;
}

在XML配置中,你可以将SpEL表达式传入 <property><constructor-arg> 的value 属性中,或者将其作为 p- 命名空间或 c- 命名空间条目的值。

1
2
3
<bean id="sgtPeppers" class="xxx.BlankDisc" 
c:_title="#{systemProperties[disc.title]}"
c:_artist="#{systemProperties[disc.artist]}" />
(2)SpEL所支持的基础表达式
  • 表示字面值:SpEL可以用来表示 浮点数、String值以及Boolean值。但很少这样用,几乎所有情况下字面值并不需要使用SpEL。

    1
    2
    3
    4
    #{3.1415}
    #{9.87E4}
    #{'Hello'}
    #{false}
  • 引用bean、属性和方法:SpEL可以通过ID引用其他的bean。

    1
    2
    3
    4
    5
    6
    7
    #{sgtPeppers}
    #{sgtPeppers.artist}
    #{artistSelector.selectArtist()}
    #{artistSelector.selectArtist().toUpperCase()}
    // 为了避免出现NullPointerException,可以使用类型安全的运算符:
    #{artistSelector.selectArtist()?.toUpperCase()}
    // 如果selectArtist()的返回值是null的话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是null
  • 在表达式中使用类型:在SpEL中访问类作用域的方法和常量的话,要依赖 T() 这个关键的运算符。

    1
    2
    3
    4
    5
    // T()运算符的结果会是一个Class对象,代表了java.lang.Math
    T(java.lang.Math)
    // 如果需要的话,我们甚至可以将其装配到一个Class类型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型的静态方法和常量。
    T(java.lang.Math).PI
    T(java.lang.Math).random()

概述了SpEL运算符:

运算符类型 运算符
算术运算 +、-、 * 、/、%、^
比较运算 < 、 > 、 == 、 <= 、 >= 、 lt 、 gt 、 eq 、 le 、 ge
逻辑运算 and 、 or 、 not 、│
条件运算 ?: (ternary) 、 ?: (Elvis)
正则表达式 matches

使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SpEL乘法运算符
#{2 * T(java.lang.Math).PI * circle.radius}
// 三元运算符(ternary)
#{scoreboard.score > 1000 ? "Winner!" : "Loser"}
// 默认值代替null,也叫Elvis运算符
#{disc.title ?: 'Rattle and Hum'}
// 正则表达式,判断字符串是否包含有效的邮箱地址
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
// 计算集合
// 引用列表中的一个元素
#{jukebox.songs[4].title}
// 随机获取列表中的一个元素
#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}
// 字符串也可以看作数组,返回第四个字符s
#{'This is a test'[3]}
// 查询运算符(.?[]),它会用来对集合进行过滤,得到集合的一个子集。
#{jukebox.songs.?[artist eq 'Aerosmith']}
// 另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在集合中查询第一个匹配项和最后一个匹配项。
#{jukebox.songs.^[artist eq 'Aerosmith']}
// 投影运算符(.![]),它会从集合的每个成员中选择特定的属性放到另外一个集合中。
#{jukebox.songs.![title]}
// 组合使用
#{jukebox.songs.?[artist eq 'Aerosmith'].![title]}

第四节 源码解析

4.1 Bean

@Bean 注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};

@AliasFor("value")
String[] name() default {};

Autowire autowire() default Autowire.NO;

String initMethod() default "";

String destroyMethod() default "(inferred)";
}

4.2 BeanDefinition

BeanDefinitionBean 的关系类似于类与对象的关系,通过 BeanDefinition 定义的数据结构,Ioc 容器能够方便的对 Bean 进行管理。BeanDefinition 就是对控制反转模式中管理的对象依赖关系的数据抽象,也是容器实现控制反转功能的核心数据结构,控制反转功能都是围绕对这个 BeanDefinition 的处理来完成的。

BeanDefinition 事实上就是 Bean 的定义在运行时的表现。无论是 xml 配置的 Bean,还是注解定义的 Bean,又或者是自定义扫描进来的 Bean,最终都通过 BeanDefinition 来承载。如果有自定义的 xml 标签,解析后也是生成 BeanDefinition 注册到 IOC 中。这样设计,IOC 只需要关心 BeanDefinition 即可,极大增加了扩展性和灵活性。当我们 getBean 的时候,如果 Bean 还没有初始化,容器就会找到 BeanDefinition ,然后根据 BeanDefinition 初始化 Bean 及其依赖。

BeanDefinition 的接口定义和实现类如图所示,整体也是分层设计。基于注解的 Bean 定义使用 AnnotatedBeanDefinition 描述,定义了获取具体 Method 的方法。

该接口定义了一些构造Bean需要的方法,可以获取以及设置Bean的基本信息。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;

void setParentName(@Nullable String var1);

@Nullable
String getParentName();

void setBeanClassName(@Nullable String var1);

// 获取Bean的类名称
@Nullable
String getBeanClassName();

void setScope(@Nullable String var1);

// 获取作用域
@Nullable
String getScope();

void setLazyInit(boolean var1);

// 是否懒加载
boolean isLazyInit();

void setDependsOn(@Nullable String... var1);

@Nullable
String[] getDependsOn();

void setAutowireCandidate(boolean var1);

boolean isAutowireCandidate();

void setPrimary(boolean var1);

boolean isPrimary();

void setFactoryBeanName(@Nullable String var1);

// 获取对应的FactoryBean名称
@Nullable
String getFactoryBeanName();

void setFactoryMethodName(@Nullable String var1);

@Nullable
String getFactoryMethodName();

// 获取构造器参数,可以通过 xml、注解注入
ConstructorArgumentValues getConstructorArgumentValues();

default boolean hasConstructorArgumentValues() {
return !this.getConstructorArgumentValues().isEmpty();
}

// 获取属性参数,可以通过 xml、注解注入
MutablePropertyValues getPropertyValues();

default boolean hasPropertyValues() {
return !this.getPropertyValues().isEmpty();
}

boolean isSingleton();

boolean isPrototype();

boolean isAbstract();

int getRole();

@Nullable
String getDescription();

@Nullable
String getResourceDescription();

@Nullable
BeanDefinition getOriginatingBeanDefinition();
}

4.2.1 Resource定位

Resource定位BeanDefinition 的资源定位,由 ResourceLoader 通过统一的 Resource 接口来实现。Spring提供了适用于各种场景的默认实现,如类路径下的资源可以用 ClassPathResource 、网络上的资源可以用 UrlResource 等等。

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
public interface Resource extends InputStreamSource {
boolean exists();

default boolean isReadable() {
return true;
}

default boolean isOpen() {
return false;
}

default boolean isFile() {
return false;
}

URL getURL() throws IOException;

URI getURI() throws IOException;

File getFile() throws IOException;

default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}

long contentLength() throws IOException;

long lastModified() throws IOException;

Resource createRelative(String var1) throws IOException;

@Nullable
String getFilename();

String getDescription();
}

DefaultResourceLoader 实现了 ResourceLoader 接口,其是各种 XXResourceLoaderXXApplicationContext 的父类。

1
2
3
4
5
6
7
8
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";

Resource getResource(String var1);

@Nullable
ClassLoader getClassLoader();
}

4.2.2 BeanDefinition的载入过程

BeanDefinition 的载入过程,就是解析 Resource 对象得到 BeanDefinitionHolder 对象的过程。

BeanDefinitionHolder 的作用是根据名称或者别名持有 beanDefinition ,承载了 nameBeanDefinition 的映射信息

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class BeanDefinitionHolder implements BeanMetadataElement {
// 实际持有的 beanDefinition 对象
private final BeanDefinition beanDefinition;
// Bean 的名称
private final String beanName;
// Bean 的别名
@Nullable
private final String[] aliases;

public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName) {
this(beanDefinition, beanName, (String[])null);
}

public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, @Nullable String[] aliases) {
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
Assert.notNull(beanName, "Bean name must not be null");
this.beanDefinition = beanDefinition;
this.beanName = beanName;
this.aliases = aliases;
}

public BeanDefinitionHolder(BeanDefinitionHolder beanDefinitionHolder) {
Assert.notNull(beanDefinitionHolder, "BeanDefinitionHolder must not be null");
this.beanDefinition = beanDefinitionHolder.getBeanDefinition();
this.beanName = beanDefinitionHolder.getBeanName();
this.aliases = beanDefinitionHolder.getAliases();
}

public BeanDefinition getBeanDefinition() {
return this.beanDefinition;
}

public String getBeanName() {
return this.beanName;
}

@Nullable
public String[] getAliases() {
return this.aliases;
}

@Nullable
public Object getSource() {
return this.beanDefinition.getSource();
}

// 比较适配Bean名称
public boolean matchesName(@Nullable String candidateName) {
return candidateName != null && (candidateName.equals(this.beanName) || candidateName.equals(BeanFactoryUtils.transformedBeanName(this.beanName)) || ObjectUtils.containsElement(this.aliases, candidateName));
}

public String getShortDescription() {
StringBuilder sb = new StringBuilder();
sb.append("Bean definition with name '").append(this.beanName).append("'");
if (this.aliases != null) {
sb.append(" and aliases [").append(StringUtils.arrayToCommaDelimitedString(this.aliases)).append("]");
}

return sb.toString();
}

public String getLongDescription() {
StringBuilder sb = new StringBuilder(this.getShortDescription());
sb.append(": ").append(this.beanDefinition);
return sb.toString();
}

public String toString() {
return this.getLongDescription();
}

public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof BeanDefinitionHolder)) {
return false;
} else {
BeanDefinitionHolder otherHolder = (BeanDefinitionHolder)other;
return this.beanDefinition.equals(otherHolder.beanDefinition) && this.beanName.equals(otherHolder.beanName) && ObjectUtils.nullSafeEquals(this.aliases, otherHolder.aliases);
}
}

public int hashCode() {
int hashCode = this.beanDefinition.hashCode();
hashCode = 29 * hashCode + this.beanName.hashCode();
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.aliases);
return hashCode;
}
}

Spring 并不是直接把 XML 文件的内容转换成 BeanDefinitionHolder 。解析时先解析 XML 得到 Document 对象,Document 对象就是 XML 文件在内存里的存储形式。从 Document 对象提取数据用的是 BeanDefinitionDocumentReader

对于 Document 对象中的一个节点 Element ,是使用 BeanDefinitionParser 进行解析。开发者也可以自定义 BeanDefinitionParser 从而实现对 XML 配置的自定义解析,可以实现诸如自定义 XML 标签的功能。

4.2.3 BeanDefinition的注册

这个操作是通过调用 BeanDefinitionRegistry 接口来实现的。这个注册过程把载入过程中解析得到的 BeanDeifinition 向 Ioc 容器进行注册。

调用 registerBeanDefinition 方法解析 BeanDefinitionHolder 对象,按照 Bean 的名称、别名将 BeanDefinition 注册到 IoC 容器中,存储在 beanDefinitionMap 中。至此,容器的初始化基本完成。

registerBeanDefinition 是一个可以复用的方法,毕竟 Spring 内部注册的流程是确定的,因此 registerBeanDefinition 处于底层实现中。各种形式不同的上层实现最终都调用了同一个注册方法,殊途同归。在阿里很多软件架构中,也都采用了类似的设计,把注册配置统一收口便于管理。

4.3 BeanFactory

BeanFactory 接口源码如下:

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
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
// getBean系列获取Bean实例
Object getBean(String var1) throws BeansException;

<T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException;

Object getBean(String var1, Object... var2) throws BeansException;

<T> T getBean(Class<T> var1) throws BeansException;

<T> T getBean(Class<T> var1, Object... var2) throws BeansException;

boolean containsBean(String var1);

boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException;

@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

String[] getAliases(String var1);
}

BeanFactory 只定义了基本功能,是一个最核心的容器接口定义。在 BeanFactory 的基础上 Spring 通过继承逐层扩充容器的能力。

HierarchicalBeanFactory 接口在继承了 BeanFactory 后,增加了 getParentBeanFactory 方法,使 BeanFactory 具备了双亲IoC容器的管理功能。

ConfigurableBeanFactory 中,定义了一些对 BeanFactory 的配置功能,比如通过 setParentBeanFactory 方法设置双亲IoC容器,通过 addBeanPostProcessor 方法配置Bean后置处理器。

4.4 ApplicationContext

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();

String getApplicationName();

String getDisplayName();

long getStartupDate();

@Nullable
ApplicationContext getParent();

AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

ApplicationContext 为核心的接口系列中,ListableBeanFactoryHierarchicalBeanFactory 两个接口连接了 BeanFactory 接口定义和 ApplicationConext 应用上下文的接口定义。

ListableBeanFactory 接口中,细化了许多 BeanFactory 的接口功能,比如定义了 getBeanDefinitionNames 接口方法。对于 ApplicationContext 接口,它通过继承 MessageSourceResourceLoaderApplicationEventPublisher 接口,在 BeanFactory 的基础上添加了对高级容器特性的支持。

ApplicationContext 继承了 BeanFactory 接口,具有了容器的基本功能,同时根据上下文的特点,也用 ListableBeanFactory 接口做了功能扩展。上下文与容器的主要区别,还是体现在容器高级特性上,比如 MessageSource 实现了国际化、ResourceLoader 实现了资源加载、ApplicationEventPublisher 实现了事件机制。因此平时工作中使用上下文会多一点,一般不直接使用 BeanFactory 简单容器。

4.5 FactoryBean

BeanFactory 中,Bean 是通过 FactoryBean 来获取的。FactoryBean 是一个工厂Bean,可以生成某一个类型 Bean 的实例,它最大的一个作用是:可以让我们自定义 Bean 的创建过程。可以使用转义符 & 得到 FactoryBean 本身,用来区分通过容器来获取 FactoryBean 产生的对象和获取 FactoryBean 本身。

1
2
3
4
5
6
7
8
9
10
11
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;

@Nullable
Class<?> getObjectType();

default boolean isSingleton() {
return true;
}
}

FactoryBeanBeanFactory ,一个是 Factory,也就是 IOC 容器工厂,一个是特色类型的 Bean。所有的 Bean 都是由 BeanFactory 管理。FactoryBean 是一个能产生或者修饰对象生成的工厂 Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。这两个类型名称比较接近,很多人容易混淆,只要记住结尾区分即可,一个是工厂,一个是 Bean。

4.6 DefaultListableBeanFactory

DefaultListableBeanFactory 实际上包含了基本IoC容器所具有的重要功能,在Spring中,实际上是把 DefaultListableBeanFactory 作为一个默认的功能完整的 IoC 容器来使用的。

一个真正完整的容器在启动阶段主要做几个事情:

  1. 找到 Bean 定义,如 xml、注解等,如果是资源文件可以用 Resource 类来封装,支持 ClassPath、jar、URL 等;
  2. 初始化 Reader 注入 Resource,BeanDefinitionReader 接口定义了解析相关的方法,Spring 默认提供了很多实现类;
  3. Reader 解析 BeanDefinition,初始化后注册到容器中。

4.7 FileSystemXmlApplicationContext

主要功能已经在 AbstractXmlApplicationContext 中实现了,在 FileSystemXmlApplicationContext 中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。

如果应用直接使用 FileSystemXmlApplicationContext ,对于实例化这个应用上下文的支持,同时启动IoC容器的 refresh() 过程。这个 refresh() 过程会牵涉 IoC 容器启动的一系列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类中将它们封装好。所以,我们在 FileSystemXml 的设计中看到的只是一个简单的调用。

FileSystemXmlApplicationContext 是一个从文件系统加载 XML 的上下文实现,因此

设计了从文件系统中加载XML的功能。

解读:

可以看到,Spring 内部上下文的实现和继承关系非常复杂,难以理解。实际上,按照实现分层的思路去理解还是比较容易的,每一层只实现自己相关的功能。类似或者公用的能力都往下沉淀变为底层的基础能力,上层实现只做调用。看源码的时候,要有全局视野,哪些是公用能力,哪些是本层次定制功能,这样就会好理解一点。

4.8 refresh()

refresh 是上下文的很重要的一个操作。Spring容器的启动,初始化一些容器启动必要的资源,BeanFactory 的创建、初始化,Bean 的创建、初始化、注册、非懒加载,注册和设置国际化工具类MessageSource,注册和设置事件,等一系列过程都在这个 refresh 方法里面进行调用。

4.8.1 refresh()

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
public void refresh() throws BeansException, IllegalStateException {
// 加锁
synchronized(this.startupShutdownMonitor) {
// 一些重置操作以及初始化操作
this.prepareRefresh();
// 获取
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

// 从Spring容器获取BeanFactory(Spring Bean容器)并进行相关的设置为后续的使用做准备
this.prepareBeanFactory(beanFactory);

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}

this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}

}
}

4.8.2 prepareRefresh()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void prepareRefresh() {
// 一些重置操作以及初始化操作
// 设置Spring容器的启动时间,撤销关闭状态,开启活跃状态
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (this.logger.isInfoEnabled()) {
this.logger.info("Refreshing " + this);
}

// 初始化属性源信息(Property)
this.initPropertySources();
// 验证环境信息里一些必须存在的属性
this.getEnvironment().validateRequiredProperties();
this.earlyApplicationEvents = new LinkedHashSet();
}

4.8.3 obtainFreshBeanFactory()

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

return beanFactory;
}

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

4.8.4 prepareBeanFactory()

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
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.setBeanClassLoader(this.getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, this.getEnvironment()));
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
if (beanFactory.containsBean("loadTimeWeaver")) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

if (!beanFactory.containsLocalBean("environment")) {
beanFactory.registerSingleton("environment", this.getEnvironment());
}

if (!beanFactory.containsLocalBean("systemProperties")) {
beanFactory.registerSingleton("systemProperties", this.getEnvironment().getSystemProperties());
}

if (!beanFactory.containsLocalBean("systemEnvironment")) {
beanFactory.registerSingleton("systemEnvironment", this.getEnvironment().getSystemEnvironment());
}

}

4.8.4 prepareBeanFactory()

4.8.4 prepareBeanFactory()

4.8.4 prepareBeanFactory()

4.8.4 prepareBeanFactory()

4.8.4 prepareBeanFactory()

4.8.4 prepareBeanFactory()


第五节 Bean的创建过程

5.1 过程

  1. 实例化 BeanFactoryPostProcessor 实现类
  2. 调用 BeanFactoryPostProcessor # postProcessBeanFactory
  3. 实例化 BeanPostProcessor 实现类
  4. 调用 InstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation
  5. 实例化 Bean
  6. 调用 InstantiationAwareBeanProcessor # postProcessAfterInstantiation
  7. 调用 InstantiationAwareBeanPostProcessor # postProcessPropertyValues
  8. Bean 注入属性
  9. 调用 BeanNameAware # setBeanName
  10. 调用 BeanClassLoaderAware # setBeanClassLoader
  11. 调用 BeanFactoryAware # setBeanFactory
  12. 调用 BeanPostProcessor # postProcessBeforeInitialization
  13. 调用 InitializingBean # afterPropertiesSet
  14. 调用 Beaninit-method
  15. 调用 BeanPostProcessor # postProcessAfterInitialization

5.2 IoC容器依赖注入

在IoC容器初始化的过程中,建立 BeanDefinition 的数据映射,之后所有的依赖的注入都依托于已经存在的 BeanDefinitionBeanDefinition 通过资源定位、载入、注册流程后完成容器的初始化,接下来重点关注上下文的 getBean

AbstractApplicationContext 抽象类中有一个 getBeanFactory 方法用于返回一个 ConfigurableListableBeanFactory ,所有 BeanFactory 接口的方法实际上都委托给子类内部的 ConfigurableListableBeanFactory 实现。

1
2
3
4
5
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

}

例如 AnnotationConfigApplicationContext ,它在被构造时,内部的beanFactory实际上是由父类 GenericApplicationContext 初始化的 DefaultListableBeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
@Nullable
private ResourceLoader resourceLoader;
private boolean customClassLoader;
private final AtomicBoolean refreshed;

public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}

public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
return this.beanFactory.getBeanDefinition(beanName);
}

因此我们看某个bean是如何被加载的可以从 DefaultListableBeanFactorygetBean 方法看起,对于 DefaultListableBeanFactory 而言那些 getBean 方法实际上在 AbstractBeanFactory 这一层就都已经实现了,并且都委托给了 AbstractBeanFactory # doGetBean

1
2
3
4
5
6
7
8
9
10
11
12
13
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
NamedBeanHolder<T> namedBean = this.resolveNamedBean(requiredType, args);
if (namedBean != null) {
return namedBean.getBeanInstance();
} else {
BeanFactory parent = this.getParentBeanFactory();
if (parent != null) {
return args != null ? parent.getBean(requiredType, args) : parent.getBean(requiredType);
} else {
throw new NoSuchBeanDefinitionException(requiredType);
}
}
}

AbstractBeanFactory 源码:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

public Object getBean(String name) throws BeansException {
return this.doGetBean(name, (Class)null, (Object[])null, false);
}

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object bean;
// 尝试从缓存中拿取一个Bean实例
// Spring会在Bean还未完全初始化完毕前,通过一个ObjectFactory提前暴露出bean实例,这样为解决循环依赖提供了便利
Object sharedInstance = this.getSingleton(beanName);
if (sharedInstance != null && args == null) {
// DEBUG日志
if (this.logger.isDebugEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}

// 对FactoryBean的情况进行特殊处理
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
// 如果正在创建的bean为原型并且已经正在创建,这种循环依赖是无法解决的,要抛出异常。
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

// 如果该beanFactory中不包含要创建bean的beanDefinition,则尝试从父beanFactory中寻找。
BeanFactory parentBeanFactory = this.getParentBeanFactory();
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
String nameToLookup = this.originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}

if (args != null) {
return parentBeanFactory.getBean(nameToLookup, args);
}

return parentBeanFactory.getBean(nameToLookup, requiredType);
}

if (!typeCheckOnly) {
this.markBeanAsCreated(beanName);
}

try {
RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
this.checkMergedBeanDefinition(mbd, beanName, args);
// 有些bean是有depends-on/@DependsOn的,需要先初始化这些依赖。
String[] dependsOn = mbd.getDependsOn();
String[] var11;
if (dependsOn != null) {
var11 = dependsOn;
int var12 = dependsOn.length;

for(int var13 = 0; var13 < var12; ++var13) {
String dep = var11[var13];
if (this.isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}

this.registerDependentBean(dep, beanName);

try {
this.getBean(dep);
} catch (NoSuchBeanDefinitionException var24) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", var24);
}
}
}

// 创建单例bean。
if (mbd.isSingleton()) {
//调用父类DefaultSingletonBeanRegistry的getSingleton,具体创建bean的工作实际上仍然是回调参数中传递的ObjectFactory#getObject方法,而createBean实际上是子类AbstractAutowireCapableBeanFactory实现的。
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
// 对FactoryBean的情况进行特殊处理。
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 创建原型bean。
else if (mbd.isPrototype()) {
var11 = null;

Object prototypeInstance;
try {
// 前置处理,维护prototypesCurrentlyInCreation,加入当前bean记录。
this.beforePrototypeCreation(beanName);
// 委托给子类AbstractAutowireCapableBeanFactory来完成具体的创建bean工作。
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
// 后置处理,维护prototypesCurrentlyInCreation信息,删除当前bean记录。
this.afterPrototypeCreation(beanName);
}

// 对FactoryBean的情况进行特殊处理。
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
String scopeName = mbd.getScope();
Scope scope = (Scope)this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}

try {
Object scopedInstance = scope.get(beanName, () -> {
this.beforePrototypeCreation(beanName);

Object var4;
try {
var4 = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}

return var4;
});
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException var23) {
throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var23);
}
}
} catch (BeansException var26) {
this.cleanupAfterBeanCreationFailure(beanName);
throw var26;
}
}

// 到这里一个bean就已经创建完了,最后一步检查类型,如果不匹配会尝试转换。
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = this.getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
} else {
return convertedBean;
}
} catch (TypeMismatchException var25) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var25);
}

throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
} else {
return bean;
}
}
}

上面针对 AbstractBeanFactory # doGetBean 方法进行了源码分析,从中我们可以看出它主要会干这几件事情:

  1. 转换beanName。
  2. 尝试从缓存的单例中拿实例。
  3. 如果要创建的bean是原型模式,且已经在尝试创建,这种循环依赖是无法解决的。
  4. 当前beanFactory不包含要创建的bean的beanDefinition,会尝试从parentBeanFactory中获取。
  5. 如果当前bean有依赖(xml的话就是有depends-on,注解的话有@DependsOn),则需要先完成那些bean的创建初始化。
  6. 针对scope分类讨论创建。我们比较关心的就是单例,其次是原型。
  7. 类型检查,并且尝试转换。

我们一般比较关心的就是单例bean和原型bean的创建。
在获取单例bean时doGetBean方法会调用父类DefaultSingletonBeanRegistry#getSingleton。可以把DefaultSingletonBeanRegistry当作一个“单例bean桶”,因为它确实就是一个用来存放单例bean的桶。但是这个桶本身不关心bean到底该怎么创建,所以对于桶里还没有的bean,它将创建bean的职责通过回调ObjectFactory#getObject来完成,而AbstractBeanFactory中传递给getSingleton方法的ObjectFactory#getObject的具体实现是调用createBean,这个方法是真正创建并初始化bean的方法,由子类AbstractAutowireCapableBeanFactory完成。
对于获取原型bean则简单多了,不用关心放到桶里缓存的事情,直接调用createBean创建就是了。

所以我们接下来通过AbstractAutowireCapableBeanFactory来看一下一个Bean具体是如何创建并初始化的。

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
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;

Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}

try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}

try {
/*
* 在对象被实例化前,这里有一个短路逻辑,会调用InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation。
* 如果存在某个InstantiationAwareBeanPostProcessor的调用结果不为null,则形成了短路,接下来调用BeanPostProcessor#postProcessAfterInitialization。
*
* 实际上,一般Spring里默认就LazyInitTargetSourceCreator和QuickTargetSourceCreator可能会使得这里的短路生效。
* 大部分情况AOP还是在bean被正常实例化后通过调用postProcessAfterInitialization实现的。
*/
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}

// 创建bean的主要方法。
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}

从上面可以看到AbstractAutowireCapableBeanFactory#createBean是创建bean的主要入口方法,但仍然不是最主要在“干活”的方法。继续向下看doCreateBean方法。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException {

BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 尝试从factoryBean缓存中获取。
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 创建bean实例。
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}

/*
* Spring为了解决单例bean的循环引用问题,会在bean还没有完全初始化完毕前通过添加singletonFactory
* 使得其它bean可以拿到某个bean的实例引用。
*/
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

// 接下去初始化bean。
Object exposedObject = bean;
try {
// 填充bean中的属性。
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
/*
* 调用初始化方法,比如:
* 1. 各种aware回调
* 2. 调用BeanPostProcessor#postProcessBeforeInitialization
* 3. 调用InitializingBean#afterPropertiesSet, xml中的init-method
* 4. 调用BeanPostProcessor#postProcessAfterInitialization
*/
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}

if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
/*
* 上面的getSingleton第二个参数为false表示不会主动触发early reference的创建。
* 所以此处earlySingletonReference只有在bean创建过程中发现有别的bean与当前bean有循环依赖才不为空。
*/
if (earlySingletonReference != null) {
/*
* 如果当前bean调用initializeBean没有增强原始bean实例,则取earlySingletonReference。
*
* 举例:
* BeanA与BeanB互相依赖。Srping先创建BeanA,再创建BeanB。
* BeanA通过addSingletonFactory暴露了获取BeanA引用的途径。
*
* 在populateBean的时候需要注入BeanB,而BeanB又需要注入BeanA,
* 则在获取BeanA时会调用原先BeanA暴露的ObjectFactory,继而使得earlySingletonObjects中加入了BeanA引用。
*
* 回到BeanA的创建过程,走到此步时,发现initializeBean没有增强原始bean实例,
* 则需要取其它循环依赖bean拿BeanA时在registry留下的结果(原始bean经过SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference回调)。
*/
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 获取当前bean依赖的其它bean。
String[] dependentBeans = getDependentBeans(beanName);
// 过滤筛选出真正依赖的bean。
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
/*
* 举例:
* BeanA与BeanB互相依赖。Srping先创建BeanA,再创建BeanB。
* BeanA的创建走到这里时会抛出异常。
*
* 原因是上面的exposedObject != bean说明initializeBean方法的调用增强了原始的BeanA。
* 而BeanB中注入的BeanA很可能是原始beanA(可能会有SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference回调,
* 也就是BeanB中注入的BeanA不是此处BeanA的最终版exposedObject。
*/
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}

return exposedObject;
}

5.3 Bean的循环依赖是如何解决的

不是所有的循环依赖Spring都能够解决的。

  • 对于最简单的情况,bean为单例,且使用Autowired或者setter注入,Spring是可以解决这样的循环依赖的。通过上面的代码中我们可以看出,在一个Bean实例化后,会调用addSingletonFactory方法,在IOC容器中通过一个ObjectFactory暴露出可以获取还未完全初始化完毕的bean引用。若存在循环依赖,则依赖的bean可以在调用getBean时通过getSingleton方法获取到循环依赖的bean。
  • 但是Spring是不允许出现原型环的,举例来说,BeanA和BeanB循环依赖且scope都为prototype。因为prototype的bean,不会触发addSingletonFactory,即每次get这样的bean都会新创建一个。所以创建BeanA需要注入一个BeanB,而这个BeanB又需要注入一个新的BeanA,这样的循环依赖是没办法解决的。Spring会判断当前bean是否是prototype并且已经在创建中,然后抛出异常。
  • 对于构造器依赖,可以作一下讨论,下面讨论的bean的scope都为单例
    • 如果BeanA构造器中依赖BeanB,并且BeanA先创建,则无论BeanB以哪种形式依赖BeanA,都没办法解决这样的循环依赖。因为实例化BeanA需要先得到BeanB(此时还未提前暴露引用),BeanB依赖BeanA,但是拿不到BeanA提前暴露的引用,这就形成了无限循环。这种情况会在BeanB试图获取BeanA时在beforeSingletonCreation方法抛出异常。
    • 如果BeanA非构造器依赖BeanB,并且BeanA先创建,BeanB即使构造器依赖BeanA,也可以进行解决循环依赖。 因为这种情况BeanB可以拿到BeanA提前暴露的引用。

5.4 Aware究竟是什么

Spring中有很多XXXAware接口,从字面意思上很容易理解:就是bean能够“感知”XXX。通常这些接口的方法都是setXXX。在项目里做一个工具类实现ApplicationContextAware接口,里面可以塞一个ApplicationContext实例到静态域中,在代码中就可以很方便获取到Spring上下文进行一些操作。

那么Spring对于这些Aware接口是在哪一步调用的呢?答案其实在上面的源码分析中已经提到。在AbstractAutowireCapableBeanFactory#initializeBean方法中,Spring默认会对实现BeanNameAware, BeanClassLoaderAware, BeanFactoryAware进行回调,为它们注入beanName, classLoader, beanFactory等。

而对于更多的一些扩展,Spring基于那些processor实现了很强的可拓展性与可插拔性。比如我们非常熟悉的ApplicationContextAware接口实际上是通过ApplicationContextAwareProcessor来实际调用的,它继承了BeanPostProcessor,其中postProcessBeforeInitialization方法中会对EnvironmentAware, EmbeddedValueResolverAware, ApplicationContextAware等等一系列Aware接口的子类Bean进行回调,为其注入相关资源。

那么ApplicationContextAwareProcessor是什么时候出现在BeanPostProcessor集合中的呢?在AbstractApplicationContext#prepareBeanFactory方法中,Spring有如下代码:

1
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

也就是当Spring上下文在初始化prepareBeanFactory的时候就已经添加了ApplicationContextAwareProcessor。


参考:

🔗 《Spring 实战》
🔗 spring bean是什么
🔗 Spring中Bean的生命周期是怎样的?
🔗 Spring IOC容器创建bean过程浅析
🔗 Spring BeanDefinition 与 IoC 容器启动过程