Spring IOC容器初始化过程(一)资源定位过程
最近复习了一遍Spring IOC容器的初始化过程,结合书籍《Spring源码深度解析》总结了一下,IOC容器的初始化过程,大概分为以下三点:
-
定位资源
定位相关的配置文件,扫描相关注解
-
加载资源
将配置信息加载到内存中
-
注册
根据载入的配置信息,初始化对象,并将其装载至容器中
整体加载的时序图
工程目录
POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
测试代码
public class ServiceB {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Object serviceA = context.getBean("serviceA");
System.out.println(serviceA);
}
}
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:component-scan base-package="com.donkeys.spring"/>
<bean id="serviceA" class="com.donkeys.spring.service.ServiceA"></bean>
</beans>
资源定位过程解析
代码中,使用的是ClassPathXmlApplicationContext类去加载Sping的配置文件,所以先给出该类类图
ClassPathXmlApplicationContext构造方法
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
//根据传入的配置文件名称,调用父类的setConfigLocations方法,解析配置文件路径,
setConfigLocations(configLocations);
//refresh = true
//refresh() 方法会重启整个容器
if (refresh) {
refresh();
}
}
构造方法中一共做了2件事,首先是设置配置文件的路径,然后对整个容器进行刷新。
这里我们重点关注refresh()方法;进入refresh()方法
AbstractApplicationContext类的refresh()方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//为刷新前做准备
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//获取IOC容器,这里就是处理资源定位以配置文件加载/注册的方法
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
这里主要关注**obtainFreshBeanFactory()**方法
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//刷新IOC容器
//这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory方法,具体调用实现调用子类的refreshBeanFactory方法
refreshBeanFactory();
//获取一个新的容器
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
**obtainFreshBeanFactory()**方法总共干了2件事,
- 重置容器,refreshBeanFactory()方法中会设置相关标志,清除旧的容器,同时为Spring上下文生成一个新的容器
- 获取一个新的容器
AbstractRefreshableApplicationContext的refreshBeanFactory()方法
下面我们进入**refreshBeanFactory()**方法
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
* 该方法会将之前的bean工厂全部关闭,并初始化一个全新的bean 工厂类 用于Spring 上下文的生命周期
* bean工厂就是IOC容器
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
//判断是否之前也有容器
//如果有就销毁掉
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建一个新的工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//读取Bean对象的定义
//这里也是使用的委派设计模式
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
这里创建了新的容器工厂,同时将新的工厂传入了loadBeanDefinitions()方法中,下面来看一下在loadBeanDefinitions方法中具体做了什么操作。
AbstractXmlApplicationContext的 loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法
在AbstractRefreshableApplicationContext类的refreshBeanFactory方法中,调用了loadBeanDefinitions方法,但是这个方法它的一个抽象方法,具体实现应由子类去实现,我们在程序启动时,使用的ClassPathXmlApplicationContext类。根据文章开头的类图可以知道,这里会调用子类AbstractXmlApplicationContext的loadBeanDefinitions方法去完成本次加载
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//使用默认的beanFactory去创建 XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//设置资源的加载环境
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置资源读取器
beanDefinitionReader.setResourceLoader(this);
//设置实体解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
//初始化 bean对象定义读取器
initBeanDefinitionReader(beanDefinitionReader);
//使用初始化完成的读取器,调用loadBeanDefinitions方法
loadBeanDefinitions(beanDefinitionReader);
}
总的来说,这里只干了一件事,那就是初始化配置
//设置资源读取器
beanDefinitionReader.setResourceLoader(this);
//初始化 bean对象定义读取器
initBeanDefinitionReader(beanDefinitionReader);
-
设置资源读取器,这里设置的资源读取器就是当前这个对象本身
通过类图我们可以发现我们这个类的顶级父类ApplicationContext,继承自DefaultResourceLoader这个类,该类实现了ResourceLoader接口,说明这个类的实例化对象本身是具有资源读取器的功能的
-
初始化bean对象定义读取器,这里设置xml文件的校验方式
下面我们继续看**loadBeanDefinitions(XmlBeanDefinitionReader reader)**方法
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//从子类对象中获取到资源定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//XmlBeanDefinitionReader 读取器调用其父类的
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
先看这一行
//这行代码的具体实现是由子类完成的,通过类图可以知道AbstractXmlApplicationContext的子类为ClassPathXmlApplicationContext
//这里主要将我们最开始在构造方法中设置好的配置文件进行返回
Resource[] configResources = getConfigResources();
再看这一行
//如果返回的配置文件不为空,就将返回的已经封装好的资源文件进行读取,
if (configResources != null) {
//XmlBeanDefinitionReader 读取器调用其父类的
reader.loadBeanDefinitions(configResources);
}
至此。资源文件定位过程已经加载完成。后续就是读取和注册。整个IOC容器加载过程中最重要的是读取过程,我们可以从刚刚的定位过程来看,虽然叫定位过程,但是其实就是一个配置文件读取器的初始化过程,这个过程会设置相关的解析策略以及校验策略。最终读取器生成后,就可以将我们早早设置好的配置文件载入然后进行读取。
转载:https://blog.csdn.net/i_wonder_how_/article/details/115315339