SpringIOC 原理与应用

mark

ICO 容器的结构如上图所示,首先要让 IOC 容器去读取 Bean 的配置信息,并在容器中生成一份相应的 Bean 定义注册表,根据这张注册表去实例化 Bean,装配好 Bean 之间的依赖关系,为上层提供准备就绪的环境,Spring 提供一个配置文件描述 Bean 还有 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。

SpringIOC 容器支持的功能

1、依赖注入

2、依赖检查

3、自动装配

4、支持集合

5、指定初始化和销毁的方法

6、支持回调方法,但是需要实现 Spring 的接口,略带有侵入性

其中最核心的功能就是依赖注入和自动装配了。

SpringIOC 源码分析

Spring 作者 Rod Johnson 设计了两个接口用以表示容器。BeanFactory 和 ApplicationContext, BeanFactory 粗暴简单,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。 ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。

BeanDefinition

BeanDefinition 与 Bean 的关系,就好比类与对象的关系,类在 spring 的数据结构就是 BeanDefinition。根据 BeanDefinition 得到的对象就是我们需要的 Bean。 理解 Bean 与 BeanDefinition 是理解 spring 的整个架构的基础与关键, BeanDefinition 接口是顶级基础接口,用来描述 Bean,里面存放 Bean 元数据,比如 Bean 类名、scope、属性、构造函数参数列表、依赖的 bean、是否是单例类、是否是懒加载等一些列信息。

BeanDefinition 继承的两个接口:

  • BeanMetadataElement 接口 :BeanDefinition 元数据,返回该 Bean 的来源
  • AttributeAccessor 接口 :提供对 BeanDefinition 属性操作能力,

继承或实现 BeanDefinition 的接口:

  • AbstractBeanDefinition 类 :抽象类统一实现了 BeanDefinition 定义的一部分操作,可以说是定义了 BeanDefinition 很多默认的属性。 正是在 AbstractBeanDefinition 基础上, Spring 衍生出了一些列 BeaDefinition。

这里我们可以关注下重写的 equals ()、hashcode ()、toString () 方法

此外 initMethodName 属性,destroyMethodName 属性,这两个属性 bean 的生命周期有关,此处只提一句,后续讲解。

接下来,我们看看从 AbstractBeanDefinition 上衍生出来的几个类

  • RootBeanDefinition: 代表一个 xml,java Config 来的 BeanDefinition
  • ChildBeanDefinition: 可以让子 BeanDefinition 定义拥有从父母哪里继承配置的能力
  • GenericBeanDefinition: spring2.5 后注册 bean 首选的是 GenericBeanDefinition。GenericBeanDefinition 允许动态的设置父 bean.GenericBeanDefinition 可以作为 RootBeanDefinition 与 ChildBeanDefinition 的替代品。
  • AnnotatedBeanDefinition 接口 : 表示注解类型 BeanDefinition。有两个重要的属性,AnnotationMetadata,MethodMetadata 分别表示 BeanDefinition 的注解元信息和方法元信息 实现了此接口的 BeanDefinition 可以获取到注解元数据和方法元数据。
  • AnnotatedGenericBeanDefinition 类 : 表示 @Configuration 注解注释的 BeanDefinition 类
  • ScannedGenericBeanDefinition 类 : 表示 @Component、@Service、@Controller 等注解注释的 Bean 类

BeanDefinitionRegistry

提供向 IOC 容器注册 BeanDefinition 对象的方法,将定义 Bean 的资源文件解析成 BeanDefinition 后需要将其注入容器中,这个过程就是由 BeanDefinitionRegistry 来完成。 BeanDefinitionRegistry 继承了 AliasRegistry 接口,其核心子类有三个:SimpleBeanDefinitionRegistry、DefaultListableBeanFactory、GenericApplicationContext。

mark

  • AliasRegistry: 用于别名管理的通用型接口,作为 BeanDefinitionRegistry 的顶层接口。AliasRegistry 定义了一些别名管理的方法。
  • BeanDefinitionRegistry:BeanDefinition 的注册接口,如 RootBeanDefinition 和 ChildBeanDefinition。它通常由 BeanFactories 实现,在 Spring 中已知的实现者为:DefaultListableBeanFactory 和 GenericApplicationContext。BeanDefinitionRegistry 是 Spring 的 Bean 工厂包中唯一封装 BeanDefinition 注册的接口。BeanDefinitionRegistry 接口定义了关于 BeanDefinition 注册、注销、查询等一系列的操作。
  • SimpleBeanDefinitionRegistry:SimpleBeanDefinitionRegistry 是 BeanDefinitionRegistry 一个简单的实现,它还继承 SimpleAliasRegistry( AliasRegistry 的简单实现),它仅仅只提供注册表功能,无工厂功能。SimpleBeanDefinitionRegistry 使用 ConcurrentHashMap 来存储注册的 BeanDefinition。
  • DefaultListableBeanFactory: DefaultListableBeanFactory,ConfigurableListableBeanFactory(其实就是 BeanFactory ) 和 BeanDefinitionRegistry 接口的默认实现:一个基于 BeanDefinition 元数据的完整 Bean 工厂。所以相对于 SimpleBeanDefinitionRegistry 而言,DefaultListableBeanFactory 则是一个具有注册功能的完整 Bean 工厂。它同样是用 ConcurrentHashMap 数据结构来存储注册的 BeanDefinition。

BeanFactory

提供 IOC 的配置机制、包含 Bean 的各种定义,便于实例化 Bean、建立 Bean 之间的依赖关系、Bean 生命周期的控制等功能。 BeanFactory 是个 Factory,也就是 IOC 容器或对象工厂,FactoryBean 是个 Bean。在 Spring 中,所有的 Bean 都是由 BeanFactory (也就是 IOC 容器) 来进行管理的。但对 FactoryBean 而言,这个 Bean 不是简单的 Bean,而是一个能生产或者修饰对象生成的工厂 Bean, 它的实现与设计模式中的工厂模式和修饰器模式类似,下图是 BeanFactory 的体系结构:

mark

ListableBeanRegistry,该接口定义了访问容器中并基本信息的若干方法。查看 Bean 的个数,获取某一类型 Bean 的配置名,查看容器中是否包括某一 Bean 等方法。

HierarchicalBeanFactory,即父子级联 ICO 接口,此容器可以通过接口方法访问父容器,通过 HierarchicalBeanFactory 接口,Spring 得 IOC 容器可以建立父子关联得容器体系,子容器可以访问父容器中的 Bean,但是父容器是不能访问子容器中的 Bean 的。Spring 使用父子容器实现了很多功能,比如在 SpringMVC 中,展现层的 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中,这样展现层的 Bean 就可以引用业务层和持久层的 Bean,而持久层和业务层的却看不到展现层的 Bean。

ConfigurableBeanFactory,它在 SpringCore 中是一个很重要的接口,增强了 IOC 容器的可定制性,它定义了设置类加载器、属性编辑器、以及属性初始化后置处理器等方法。

AutowireCapableFactory,它定义了将容器中的 Bean 按照某种规则,比如按照名称匹配,按照类型匹配等,按照这些规则,对 Bean 进行自动装配。

SingletonBeanRegistry,它允许在运行期间向容器注册 Singleton 实例 Bean 的方法,通过这些接口也证明了 BeanFactory 体系确实是提供了 IOC 的基础,即依赖注入和 Bean 的装载等功能。

BeanFactory 与 ApplicationContext

BeanFactory 是 Spring 框架的基础设施,面向 SpringApplicationContext 面向使用 Spring 框架的开发者。

ApplicationContext 的功能 (继承多个接口)

  • BeanFactory:能够管理、装配 Bean
  • ResourcePatternResolver:能够加载资源文件
  • MessageSource:能够实现国际化等功能
  • ApplicationEventPublisher:能够注册监听器,实现监听机制

ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。但是也有一些限制情形,比如移动应用内存消耗比较严苛,在那些情景中,使用更轻量级的 BeanFactory 是更合理的。然而,在大多数企业级的应用中,ApplicationContext 是你的首选。

SpringIOC 应用示例

下面演示一个我们手动装配的例子:

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {
private Integer id;
private String name;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

ApplicationConfig.java

1
2
3
4
5
6
7
8
9
10
@Configuration
public class ApplicationConfig {
@Bean (name = "person")
public Person initPerson(){
Person person = new Person();
person.setId (1);
person.setName ("Tim");
return person;
}
}

SpringStudyApplication.java

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringStudyApplication {
public static void main(String [] args) {
ApplicationContext ctx = SpringApplication.run (SpringStudyApplication.class, args);
Person person = ctx.getBean (Person.class);
System.out.println ("Name is " + person.getName ());
}
}

mark

接下来使用另一种方式,这样就不需要 ApplicationConfig 了, @SpringBootApplication 已经含有了 @ComponentScan 注解,即已经拥有了扫描器的功能:

Person.java

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
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component ("person")
public class Person {
@Value ("1")
private Integer id;

@Value ("Tim")
private String name;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

mark

现在看看如果是多个实现类,如何自动注入呢?

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
public interface Pet {
void move();
}

@Component
public class Dog implements Pet {
@Override
public void move() {
System.out.println ("Running...");
}
}

@Component
public class Bird implements Pet {
@Override
public void move() {
System.out.println ("flying...");
}
}

@Component ("person")
public class Person {
private Pet pet;

public Person(Pet pet) {
this.pet = pet;
}

...
}

此时应该注入哪个呢?其实 IOC 容器也不知道该注入谁,只能报错。

1
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

@Reource(默认按名称装配,当找不到与名称匹配的 bean 才会按类型装配), @Autowired(默认按类型装配,如果我们想使用按名称装配,可以结合 @Qualifier 注解和 @Primary 注解一起使用)

@Primary 自动装配时当出现多个 Bean 候选者时,被注解为 @Primary 的 Bean 将作为首选者,否则将抛出异常

@Qualifier 的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的

mark