Spring源码阅读系列之初始化容器整体流程介绍

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

上篇文章提到,因为 Spring 源码非常庞大,所以阅读 Spring 源码的最直接有效的办法就是 Debug,后续源码也是基于这种方式去读。接下来看看一个最简单的例子

  1. 创建一个UserService接口和其实现类
public interface UserService {
void save();
}

public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("执行user信息保存操作");
}
}

  1. 创建一个applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
>

<bean id="userService" class="UserServiceImpl"></bean>
</beans>

  1. 编写一个 JUnite 测试类
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JuniteTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 1.通过byType的方式获取bean实例
UserService service = context.getBean(UserService.class);
service.save();
System.out.println("-----------");
// 2.通过byName的方式获取bean实例
UserService service1 = (UserService) context.getBean("userService");
service1.save();
}
}

  1. 从 Junite 测试类 创建Context开始,debug 进去可以看到下面这段代码
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)

throws BeansException
{

super(parent);
setConfigLocations(configLocations);
if (refresh) {
// 这里就是真正初始化Spring容器的地方,也就是我在上一篇文章最后提供的那张图里的流程
// 有同学会问,那Tomcat启动的时候是怎么初始化Spring容器的呢?
// 其实也比较简单,一般在Tomcat里面的web.xml文件配置了一个监听器,大概长这个样子
//<listener>
// <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
//</listener>
// 该监听器调用contextInitialized()方法,一路点进去可以看到最后也是调用了AbstractApplicationContext.refresh()方法
refresh();
}
}

  1. 找到了 Spring 容器初始化的入口,接下来就来详细看看我们单测里面用到的userService实例是怎么被创建出来的
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// Step1:其主要作用就是为当前的context上下文初始化一些基础参数
// 比如说:初始化时间,活动状态,日志信息等等,暂时不重要,不需要关注其太多的实现细节
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
// Step2: 从注释上看是通知子类去刷新内部bean工厂。解释的好像也特别抽象不知道它到底想干啥
// **重要** 其实在这个方法里面做了几个非常重要的事情,创建bean工厂,并将BeanDefinitions对象(bean实例的定义,这个对象也会贯穿整个Spring源码)存到bean工厂中
// 创建出来的ConfigurableListableBeanFactory,这个bean工厂会贯穿整个Spring源码
// 它功能是存放beanDefinition定义,后续创建出来的bean实例也存放这这个工厂里面
// 暂时先理解到这里就可以了,后续我们会展开其里面的具体实现
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
// Step3:配置bean工厂的上下文,
// 比如在这个里面配置了ClassLoader,
// 配置了BeanPostProcessor(bean后置处理器,这玩意儿会在bean实例初始化前后做一些增强工作,后面会详细介绍)
// 注册了三个默认bean实例,分别是 “environment”、“systemProperties”、“systemEnvironment” 了解即可
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
// Step4:本方法没有具体实现,是一个扩展点,开发人员可以根据自己的情况做具体的实现
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
// Step5:bean工厂后置处理器,主要是对beanFactory对象做一些后置操作
// 和registerBeanPostProcessor名字有点像,只不过后者操作的对象bean实例
// 后续会深入了解其实现原理
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
// Step6:注册所有实现了BeanPostProcessor接口的对象,
// 注意:这里只是注册实例,并没有执行其方法,方法具体调用时机是在bean实例初始化前后执行的
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
// Step7:初始化message资源,做一些国际化相关操作,了解即可
initMessageSource();

// Initialize event multicaster for this context.
// Step8:初始化事件广播器,用于发送相关事件
// 比如注册了一个bean实例的时候就会发送一个事件
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
// Step9:在一些特定的context中初始化一些特殊的bean,没有具体实现,留给开发者自定义一些操作
onRefresh();

// Check for listener beans and register them.
// Step10:注册监听器,了解即可
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
// Step11:初始化所有非懒加载的bean实例,注意是非懒加载的bean
//**非常重要** 我们代码中需要用到的bean实例几乎都是从这里创建出来的,后面会展开其具体实现
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
// Step12:完成上线文刷新,发布上下文初始化完毕的事件
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.
// 当创建bean发生异常时,先销毁已经创建出来的单例bean实例,从而避免资源一直被占用。
// 这样的做法有点像关闭资源链接,比如当程序联系MySQL或Redis的时候,程序发生异常时需要将链接断开一样
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...
// Step13:重置公共的一些缓存数据
resetCommonCaches();
}
}
}

总结

Spring 容器初始化,共经历了 13 步;其中尤其需要重点关注的是:

  • Step2,初始化 Spring 容器,并构建了BeanDefinition定义
  • Step5BeanFactoryPostProcessor,对BeanFactory做一些后置操作
  • Step7BeanPostProcessor,对 bean 实例在初始化前后做一些增强工作Step11,对剩余所有的非懒加载的BeanDefinition(bean 定义)执行 bean 实例化操作

下一篇文章我们来讲讲 Step2,obtainFreshBeanFactory()实现原理。


Spring源码阅读系列之初始化容器整体流程介绍

欢迎关注我,共同学习

原文始发于微信公众号(z小赵):Spring源码阅读系列之初始化容器整体流程介绍