Spring IoC 知识图

一、概述

1. IoC 与 DI

IoC: 控制反转。把对象创建(实例化、管理)的控制权交给外部环境(spring 框架、IoC 容器)
DI: 依赖注入。容器把被依赖对象注入到依赖对象中

2. BeanFactory 与 ApplicationContext 区别

2.1. BeanFactory 基础容器

  • 顶层容器(根容器),不能被实例化。
  • 加载配置文件,解析成 BeanDefinition 放在 Map 里。
  • 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。

2.2. ApplicationContext 高级容器

  • 包含了基础容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。
  • 支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等
  • 即时加载、自己创建和管理资源对象、支持国际化、支持基于依赖的注解

二、XML 方式

1. 实例化 Bean 方式

1.1. 使用无参构造函数

1
2
<!-- 配置 service 对象 --> 
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl"> </bean>

1.2. 使用静态方法创建

class 属性: 指定静态方法所在的类
factory-method: 指定静态方法名

1
2
<!-- 使⽤静态⽅法创建对象的配置⽅式 --> 
<bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>

1.3. 使用实例化方法创建

factory-bean: 指定实例化方法所在的 bean
factory-method: 指定 factory-bean 中的实例化方法

1
2
3
<!-- 使⽤实例⽅法创建对象的配置⽅式 --> 
<bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>

2. 属性装配

2.1. 构造函数注入

构造函数:

1
2
3
4
5
6
7
public class JdbcAccountDaoImpl {
...
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name) {
this.connectionUtils = connectionUtils;
this.name = name;
}
}

对应 bean 配置:

1
2
3
4
<bean id="userService" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"> 
<constractor-arg name="connectionUtils" ref="connectionUtils"></constractor-arg>
<constractor-arg name="name" value="zhangsan"></constractor-arg>
</bean>

2.2. set 方法注入

property 对应类中的字段属性

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
<bean id="userService" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl"> 
<property name="connectionUtils" ref="connectionUtils"></property>
<property name="name" value="zhangsan"></property>
<!-- list -->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
</array>
</property>
<!-- set -->
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<!-- map -->
<property name="myMap">
<map>
<entry key="key1" value="value1" />
<entry key="key2" value="value2" />
</map>
</property>
<!-- property -->
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
</bean>

三、注解方式

1. 实例化 Bean 方式

  • _@Configuration:_表明该类为配置类
  • _@ComponentScan:_这个注解能够在 Spring 中启用组件扫描。 默认会扫描与配置类相同的包
  • _@PropertySource:_引⼊外部属性配置⽂件
  • _@Import:_引⼊其他配置类
  • _@ImportResource:_引用 XML 配置
  • _@Value:_对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
    1
    2
    3
    4
    5
    6
    @Configuration
    @ComponentScan
    @Import({CDPlayerConfig.class, CDConfig.class})
    @ImportResource("classpath:cd-config.xml")
    public class SoundSystemConfig {
    }

1.1. 组件实现实例化

通过 @ComponentScan 类路径扫描来自动侦测以及自动装配到 Spring 容器中
用于自定义类

  • @Component:基本注解,标识了一个受 Spring 管理的组件
  • @Respository:标识持久层组件
  • @Service:标识服务层(业务层)组件
  • @Controller:标识表现层组件

1.2. @Bean 实现实例化

在 @Configuration 定义的配置类中使用可注册并获得 bean
通常同于第三方插件实例化

1
2
3
4
5
6
7
8
9
@Configuration
@ComponentScan
public class CDPlayConfig {

@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}

2. 属性装配

  • @Autowired: 按照类型注⼊

  • @Qualifier: 告诉 Spring 具体去装配哪个对象

    1
    2
    3
    4
    5
    6
    @Service
    public class TransferServiceImpl {
    @Autowired
    @Qualifier(name="jdbcAccountDaoImpl")
    private AccountDao accountDao;
    }
  • @Resource: 注解由 J2EE 提供,默认按照 ByName ⾃动注⼊

  • @Primary: 可以通过 @Primary 来表达最喜欢的方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Component
    @Primary
    public class IceCream implements Dessert {
    ...
    }

    // Bean 方法
    @Bean
    @Primary
    public Dessert iceCream() {
    return new IceCream();
    }

四、启动容器方式

1. Java 环境下启动 IoC 容器

  • ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
  • AnnotationConfigApplicationContext:纯注解模式下启动 Spring 容器

2. Web 环境下启动 IoC 容器

从 xml 启动容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>

<!-- 配置 Spring IoC 容器的配置⽂件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!-- 使⽤监听器启动 Spring 的 IoC 容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

从配置类启动容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>

<!-- 告诉 ContextloaderListener 知道我们使⽤注解的⽅式启动 IoC 容器 -->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>

<!-- 配置启动类的全限定类名 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>

<!-- 使⽤监听器启动 Spring 的 IoC 容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

五、bean 的作用域

1. 类型

  • 单例(Singleton): 在整个应用中,只创建 bean 的一个实例
  • _原型(Prototype)_: 每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例
  • 会话(Session): 在 Web 应用中,为每个会话创建一个 bean 实例
  • 请求(Request): 在 Web 应用中,为每个请求创建一个 bean 实例

2. 实现

xml:

1
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl" scope="prototype"> </bean>

注解:

1
2
3
4
5
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {
...
}

六、高级特性

1. lazy-Init

_懒加载:_不会在容器初始化时加载这个 bean ,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化
xml:<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
注解:@Lazy

2. FactoryBean

(63 条消息) Spring - FactoryBean 的使用场景和源码_阿里巴巴首席技术官的博客 - CSDN 博客_factorybean 使用场景
可以⽣成某⼀个类型的 Bean 实例,可以借助于它⾃定义 Bean 的创建过程

CompanyFactoryBean:

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
// 可以让我们⾃定义 Bean 的创建过程(完成复杂 Bean 的定义)
public class CompanyFactoryBean implements FactoryBean<Company> {
/** 公司名称,地址,规模 */
private String companyInfo;

public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}

// 返回 FactoryBean 创建的Bean实例,如果 isSingleton 返回 true,则该实例会放到 Spring 容器的单例对象缓存池中 Map
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象
Company Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));

return company;
}

// 返回 FactoryBean 创建的 Bean 类型
@Override
public Class<?> getObjectType() {
return Company.class;
}

// 返回作⽤域是否单例
@Override
public boolean isSingleton() {
return true;
}
}

bean 定义:
当获取 id 为 companyBean 的 bean 时会返回 Company 类型的 bean
获取 FactoryBean,需要在 id 之前添加 “&”,如:”&companyBean”

1
2
3
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean"> 
<property name="companyInfo" value="xx,中关村,500"/>
</bean>

3. profile bean

1
2
3
4
5
6
7
8
9
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
  • spring.profiles.active: 如果设置了 spring.profiles.active 属性的话,那么它的值就会用来确定哪个 profile 是激活的
  • spring.profiles.default: 如果没有设置 spring.profiles.active 属性的话,那 Spring 将会查 spring.profiles.default 的值

设置属性方式:

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

4. 条件化配置 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
// 条件化创建 Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}

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

通过 ConditionContext,我们可以做到如下几点:

  • 借助 getRegistry () 返回的 BeanDefinitionRegistry 检查 bean 定义
  • 借助 getBeanFactory () 返回的 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至探查 bean 的属性
  • 借助 getEnvironment () 返回的 Environment 检查环境变量是否存在以及它的值是什么
  • 读取并探查 getResourceLoader () 返回的 ResourceLoader 所加载的资源
  • 借助 getClassLoader () 返回的 ClassLoader 加载并检查类是否存在

5. 使用 Enviroment 注入

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

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

七、Bean 生命周期

  1. 容器启动后会创建 BeanFactory,并加载配置的 bean 信息成 BeanDefinition 放入 Map
    这之后会执行自定义的 Bean 工厂后置处理器(实现 BeanFactoryPostProcessor 接口)中的 postProcessBeanFactory 方法。可从中获得 bean 配置信息(BeanDefinition)

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
    ...
    }
    }
  2. 这之后会对 scope 为 singleton 且非懒加载的 bean 进行实例化

  3. 按照 Bean 定义信息配置信息,注入所有的属性

  4. 如果 Bean 实现了 BeanNameAware 接口,会回调该接口的 setBeanName () 方法,传入该 Bean 的 id。此时该 Bean 就获得了自己在配置文件中的 id

  5. 如果 Bean 实现了 BeanFactoryAware 接口,会回调该接口的 setBeanFactory () 方法,传入该 Bean 的 BeanFactory。这样该 Bean 就获得了自己所在的 BeanFactory

  6. 如果 Bean 实现了 ApplicationContextAware 接口,会回调该接口的 setApplicationContext () 方法,传入该 Bean 的 ApplicationContext。这样该 Bean 就获得了自己所在的 ApplicationContext

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Component
    public class Result implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    @Override
    public void setBeanName(String name) {
    System.out.println("该对象注册成 bean 时定义的 id:" + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    System.out.println("该对象的 beanFactory:" + beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("applicationContext:" + applicationContext);
    }
    }
  7. 如果有 Bean 实现了 BeanPostProcessor 接口,会回调该接口的 postProcessBeforeInitialzation () 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
    * init-method 方法执行之前被调用
    */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
    if ("lazyResult".equalsIgnoreCase(beanName)) {
    System.out.println("MyBeanPostProcessor before 方法拦截处理 lazyResult");
    }
    return bean;
    }
    }
  8. 如果 Bean 实现了 InitializingBean 接口,则会回调该接口的 afterPropertiesSet () 方法,@PostConstruct 注解的方法在该方法之前被调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class Result implements InitializingBean {

    // 在 afterPropertiesSet 之前执行
    @PostConstruct
    public void postConstruct() {
    System.out.println("postConstruct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    System.out.println("afterPropertiesSet");
    }
    }
  9. 如果 Bean 配置了 init-method 方法,则会执行 init-method 配置的方法,@PostConstruct 注解的方法在该方法之前被调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    public class Result {

    // 在 afterPropertiesSet 之后执行
    public void initMethod() {
    System.out.println("initMethod");
    }
    }

    <bean id="lazyResult" class="com.xxx.beans.Result" init-method="initMethod"></bean>
  10. 如果有 Bean 实现了 BeanPostProcessor 接口,则会回调该接口的 postProcessAfterInitialization () 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
    * initmethod 方法执行之后被调用
    */
    @Override
    public Object postProcessAfterInitialization(Object beanm String beanName) {
    if ("lazyResult".equalsIgnoreCase(beanName)) {
    System.out.println("MyBeanPostProcessor after 方法拦截处理 lazyResult");
    }
    return bean;
    }
    }
  11. 使用该 Bean

  • scope 为 singleton 的 Bean:Spring 的 IoC 容器中会缓存一份该 bean 的实例(singletonObjects 中)
  • scope 为 prototype 的 Bean:每次使用都会 new 一个新的对象,对象的生命周期不归 IoC 容器管理,对象的销毁交给垃圾回收器
  1. 容器关闭后,如果 Bean 实现了 DisposableBean 接口,则会回调该接口的 destroy () 方法,@PreDestory 注解的方法在该方法之前被调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class Result implements DisposableBean {

    // 在 destroy 之前执行
    @PreDestroy
    public void preDestroy() {
    System.out.println("preDestroy");
    }

    @Override
    public void destroy() throws Exception {
    System.out.println("destroy");
    }
    }
  2. 如果 Bean 配置了 destroy-method 方法,则会执行 destroy-method 配置的方法

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class Result {

    // 在 destroy 之后执行
    public void destroyMethod() {
    System.out.println("destroyMethod");
    }
    }

八、源码分析

Spring IoC 的容器体系:

1. 容器初始化分析

1
2
// 容器初始化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

最终调用 AbstractApplicationContext#refresh 方法进行容器初始化

refresh 主要流程:

  1. 初始化工厂
  • obtainFreshBeanFactory 方法: 获取 DefalutListableBeanFactory 工厂、解析 xml 加载 BeanDefinition 并注册到 BeanDefinitionRegistry
    1. 调用 refreshBeanFactory 方法:关闭以前的工厂,初始化一个新工厂(DefaultListableBeanFactory)并自定义属性
    2. 调用 **loadBeanDefinitions** 方法:最终调用 XmlBeanDefinitionReader#loadBeanDefinitions 加载 BeanDefinition
  • invokeBeanFactoryPostProcessors 方法: 生成实现了 BeanFactoryPostProcessor 的 bean,并调用其方法
  1. 初始化 Bean
  • registerBeanPostProcessors 方法: 注册 BeanPostProcessor 后置处理器(目前阶段尚未执行)
  • finishBeanFactoryInitialization 方法:
    调用 ConfigurableListableBeanFactory#preInstantiateSingletons 来实例化所有非懒加载的单例 bean
    遍历所有 beanName,若是非抽象、单例、非懒加载的 beanDefinition 则调用 **AbstractBeanFactory#getBean**

下面重点介绍 loadBeanDefinitionsgetBean 方法

1.1. loadBeanDefinitions

XmlBeanDefinitionReader#loadBeanDefinitions
作用: 加载 bean 配置信息

重要步骤:

    1. doLoadDocument:获取 XML DOM 文档
    1. registerBeanDefinitions:从 DOM 文档中读取 beanDefinition 并将它们注册到 readerContext 里的注册表中
      1. createDelegate:创建委托类进行初始化
      1. parseBeanDefinitions:解析标签注册 beanDefinition

1.2. getBean

获取 bean 方法,从中可以看出 bean 的生命周期

重要步骤:

    1. getSingleton:分别从一二三级缓存中获取 bean,若有缓存则直接返回
    1. getSingleton:生成一个新的 bean 实例
      1. 加锁并再次从缓存中获取,与 2) getSingleton 组成双重检验锁(DCL)
      1. createBean:创建 bean 并进行一系列初始化操作,实际调用 doCreateBean 方法
        1. createBeanInstance:创建 bean 实例,但尚未设置属性
        1. addSingletonFactory:将该 bean 加入三级缓存 singletonFactories 中,提前暴露该 bean 的引用
        1. populateBean:为 bean 设置属性值,若遇到依赖的 bean 会调用 13) resolveValueIfNecessary 方法,内部会重新调用 getBean 方法来获取依赖 bean 的引用
          由于最初要获取的 bean 已经加入三级缓存暴露了引用,所以其依赖的 bean 若出现循环引用则会调用 2) getSingleton 从三级缓存中获取该引用,并把该引用转移到二级缓存中
        1. initializeBean:初始化 bean
        • invokeAwareMethods:调用 setBeanName、setBeanFactory、setApplicationContext 三个 aware 类型的方法
        • applyBeanPostProcessorsBeforeInitialization:后置处理器的 before 方法(与初始化方法)
        • invokeInitMethods:初始化方法
        • applyBeanPostProcessorsAfterInitialization:后置处理器的 after 方法(后初始化方法)
      1. registerDisposableBeanIfNecessary:注册销毁方法
      1. addSingleton:初始化 bean 后将其放入一级缓存以便后续使用

2. 循环依赖

概述:
循环引用,两个或两个以上的 Bean 互相持有对方,最终形成闭环

解决:
采用三级缓存。在 bean 创建后尚未设置属性时先将 bean 的引用(称为早期引用)暴露,这样后续依赖的 bean 就能获取到这个引用。

  • 因为需要将 bean 的早期引用放入缓存,所以无法解决构造器注入下的循环依赖
  • 因为 prototype 原型 bean 生成实例后不被容器管理,所以也无法解决 scope 为 prototype 的 bean 循环依赖

原理:

  • 三级缓存 singletonFactories:bean 实例化后尚未设置属性时放入三级缓存中提前暴露引用
  • 二级缓存 earlySingletonObjects:当 A 对象依赖 B 对象,B 对象又依赖 A 对象时,B 可从三级缓存中获取 A 的早期引用,并放入二级缓存。在这过程中可做一些扩展操作
  • 一级缓存 singletonObjects:存放完整初始化后的 bean

九、其他

其他资料:

面试题: