Spring IOC(2) 容器的的创建的工作
在IOC(1)中介绍了Spring Bean
的生命周期,并引出了AbstractApplicationContext
#refresh
方法,本节将介绍refresh
方法中的
prepareRefresh
:创建容器前的工作。obtainFreshBeanFactory
: 创建BeanFactory
。prepareBeanFactory
: 准备BeanFactory
。
prepareRefresh 方法
Spring的前戏准备大概就是做了以下几件事:
- 设置容器的启动时间
- 设置容器的停止状态为false
- 设置容器的激活状态为true
- 获取环境信息并验证必要的属性
- 准备监听器和事件的容器
1 | protected void prepareRefresh() { |
环境信息是何时设置进去的呢?
在容器启动时调用了父类构造函数时设置进去的,Environment
他是一个接口,他有个重要的实现类叫StandardEnvironment
,在Spring启动的时候就会使用这个类进行环境信息的加载,最终他会调用到System#getProperties
和System#getenv
方法,然后将加载到属性放在Map中进行保存。
要加载的信息就是系统的环境变量,比如在Windows
中配置的环境变量或者启动类中使用-D
参数配置的启动参数都会进行加载到StandardEnvironment
这个类中,类似于使用-Dxxx.name=123
这种参数会加载到systemProperties
中,配置的windows
环境变量会加载systemEnvironment
中。
创建BeanFactory
Spring创建 BeanFactory 的方式
按照Bean
的配置方式手动创建可以分为两种:
使用
XMl
配置的Bean
这种方式使用
xml
配置文件配置Bean
的信息并且设置扫描的路径,扫描到的包可以使用注解进行配置Bean
信息,一般来说手动创建BeanFactory
容器的实现类为ClassPathXmlApplicationContext
和SystemFileXmlApplicationContext
,设置xml
的路径即可创建出IOC
容器。1
2ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
User user = context.getBean(User.class);使用注解配置的
Bean
这种方式不使用
xml
配置文件,全部基于注解方式配置Bean
的信息,比如使用@Component
、@Configuration
进行Bean
的配置,实现类为AnnotationConfigApplicationContext
设置扫描的包,然后调用refresh
方法进行IOC
容器的创建。1
2
3AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.redwinter.test");
context.refresh();
我们在开发中一般是使用web
容器进行IOC容器的创建,例如tomcat
容器、jetty
容器、undertow
容器、netty
容器。
Spring
中有一个BeanFactory
的实现类:GenericApplicationContext
,他的子类有一个叫GenericWebApplicationContext
,在Spring Boot
中,就是通过实现这个类完成Web
容器的创建+IOC
容器的创建的。在Spring Boot
中有个类叫ServletWebServerApplicationContext
就是继承了GenericWebApplicationContext
这个类,然后ServletWebServerApplicationContext
中有个属性叫webServer
,这个是一个接口,这个接口对应的实现就是Web
容器的实现:
1 | public class ServletWebServerApplicationContext extends GenericWebApplicationContext |
创建IOC容器
Spring Bean IOC
的创建流程中的第二个方法:
1 | ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); |
这个方法会创建一个DefaultListableBeanFactory
,默认的可列出Bean
的工厂,主要功能是刷新BeanFactory
1 | // AbstractApplicationContext |
BeanFactory的创建流程分为:
- 首先判断是否已经有
BeanFactory
了,如果有就销毁掉并且关闭工厂。 - 直接创建一个
BeanFactory
,默认就是使用DefaultListableBeanFactory
,不过在创建的过程中可能会默认初始化一些属性,比如:allowBeanDefinitionOverriding
和allowCircularReferences
允许Bean
覆盖和解决循环依赖的问题,还有就是BeanFactory
的序列化id等属性。 - 设置序列化
id
customizeBeanFactory(beanFactory)
,定制BeanFactory
,这里是一个扩展点,你可以对BeanFactory
进行定制- 加载
BeanDefinition
,这里从由他的子类进行实现。例如XML,Annoation等多种加载的方法。
1 | protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) |
至此BeanFactory初始化完成
定制个性化的BeanFactory
在customizeBeanFactory(beanFactory)
这个方法中,spring
设置了两个属性,一个是设置是否可以覆盖Bean
,一个是否允许循环依赖。
1 | protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { |
我们新建一个类继承ClassPathXmlApplicationContext
,然后重写customizeBeanFactory
这个方法:
1 | /** |
这样当Spring
发现我们在代码出现循环依赖,就会在创建Spring IOC
时报错。
BeanDefinition 的加载
在刷新BeanFactory
的方法中,有个方法叫loadBeanDefinitions
,这个方法就是进行BeanDefinition
的加载的,他的大致流程是这样的:

在Spring
中一般解析XML
文件的时候都是从网上下载对应的标签解析,比如Spring
配置文件中的https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
,但是一般来说都是不需要进行下载的,Spring
提供了本地文件的xsd
文件,这些xsd
文件就配置在META-INF/spring.schemas
文件中进行配置,由于文件中内容比较多我就不复制出来了。
在Spring
进行xml
解析之前会创建一个namespace
的处理器的解,解析完之后就会调用注册,将解析到的BeanDefinition
放在beanDefinitionMap
和beanDefinitionNames
集合中,最终完成了BeanDefinition
的加载过程。
现在开发基本都是使用Spring Boot
,是全注解方式,这种BeanDefinition
的加载实际上就是指定了一个包的扫描,然后扫描这些包下标记了
解析的主要流程在AnnotationConfigApplicationContext
这个类中。
准备BeanFactory
AbstractApplicationContext#refresh
方法的第三个方法:prepareBeanFactory
,准备BeanFactory
。
1 | protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { |
这个方法中主要做了以下事情:
- 设置
BeanFactory
的类加载器。 - 设置Bean的
SPEL
表达式的解析器,其作用是对值进行表达式的解析,比如在属性填充时,针对值是Properties或者String类型的时候就会使用el表达式进行解析。 - 设置属性编辑器的注册器,作用是对属性进行解析,比如在属性填充时,针对字符串String类型的时候进行类型转换,就可以自定义属性编辑器针对性的进行解析操作。
- 添加一些内置的
BeanPostProcessor
用于后面对象初始化时调用。 - 设置环境信息,系统属性,系统环境变量等。
这个方法预留了一些扩展点,比如可以添加自定义的属性编辑器,添加自定义的BeanPostProcessor
等。
参考: