SpringBoot成长记5:Spring容器的创建
前面你熟悉了SpringBoot的扩展点SpringApplicationRunListeners的设计,配置文件ConfigurableEnvironment的抽象封装。其实这些都还不是它最核心的,最最核心的时Spring的容器的创建和准备,自动配置的装配,tomcat的容器的启动。
这一节我们就来开始研究Spring的容器相关的逻辑,看看它有什么抽象的设计和扩展点,又主要做了哪一些事情呢?
public ConfigurableApplicationContext run(String... args) {
//扩展点 SpringApplicationRunListeners listeners.starting();
//配置文件的处理和抽象封装 ConfigurableEnvironment
//容器相关处理
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] {ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
refreshContext(context);
//其他逻辑
}
容器相关的逻辑主要由三个方法组成:createApplicationContext()、prepareContext()、refreshContext()。
这三个方法,每一个都做了很多事情,我们一个个来分析下。
Spring容器的创建时的核心组件
createApplicationContext容器创建的逻辑,其实非常简单,就是通过反射创建了一个容器的实现类,默认创建AnnotationConfigServletWebServerApplicationContext。
代码如下:
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
你可能第一次看这个方法,只是这样知道了反射创建了一个容器的实现类可能就结束了。
你其实可以看下这个类反射创建也是会走构造函数的,所以可以简单分析下创建时,初始化了那些组件。
你跟着成长记,分析了很多对象的创建,你会发现,分析这个类的脉络,或者组件,画一个图,基本都已经形成一个固定的分析套路了。
//自身构造函数
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
//父类构造函数
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
主要创建的是DefaultListableBeanFactory,Reader和一个Scanner。
之前普及过一个Spring基本术语,大家还记得么?容器通常称为ApplicationContext或者BeanFactory,context也简称为容器。ApplicationContext包装了BeanFactory,封装更高级的API而已,有各种实现类。
所以DefaultListableBeanFactory这个就是真正的容器对象。
术语普及BeanDefinition、BeanDefinitionRegistry、InternalBean
这里涉及到了新的术语普及BeanDefinition、BeanDefinitionRegistry。
BeanDefinition表示了其实就是我们交给对Spring容器管理对象,也就是Bean的描述和定义,比如这个Bean是否是单例、是否有依赖其他Bean、依赖了那些Bean、Bean的名称、别名等等。
BeanDefinitionRegistry封装了对BeanDefinition常见操作的接口,容器默认实现了这个接口,所以一般它也代表了容器。容器BeanFactory实现了它,可以通过实现的方法,维护List
InternalBean 其实是指Spring自己注入的相关Bean,用来实现SpringBoot的核心功能的。
Context的构造函数除了创建DefaultListableBeanFactory容器,而Reader和一个Scanner他们各自有自己的创建流程。我简单画个图给大家概括下:
上图概括了Reader和Scanner的组件,其中最最核心的是内部Bean的加载,其他的组件都是为了容器服务的而已。
总而言之,可以看到容器创建时候,Reader和一个Scanner是负责扫描和解析注解的,可以扫描所有相关Bean定义的注解,并且自己还会补充内部提前定义的一些Bean。并且这些internalBean都是默认写死的一些BeanDefination,隐藏在构造函数汇中加载的一步,这一步非常关键,SpringBoot核心功能的实现点都是靠写bean来实现的,后面我们会看到的。
从Reader和Scanner开始思考下Spring容器的抽象设计
知道这个AnnotationConfigServletWebServerApplicationContext容器的核心组件后,我们可以思考下,这些组件是用来做什么的?之间的关系是啥,主要抽象出来是为了做什么呢?
你可以想象下,Spring容器的目的是为了IOC,也就是帮我们维护大量Bean对象的创建和管理。那么核心问题就是,这些Bean是怎么找到,并且创建到容器中的呢?
这就关系到了很关键的抽象设计。它就是从Resource->ClassLoader->Reader/Scanner->BeanDefination的设计了。
Resource
我来具体解释下,Resource顾名思义,表示的是各种资源。包括了各种Bean描述文件,如java类中@Bean @Configuration的定义,@Service和@Controller的定义,又比如xml中
ResourceLoader(ClassLoader)
这些资源都是在classpath下面的,只要通过ClassLoader我们可以扫描到和加载到的。
Reader
但是由于这些资源的定义bean的格式各种各样,此时就需要一个抽象,来统一解析这些资源,所以就有了Reader和Scanner了,不同的Reader解析不同的文件,如xmlReader,groovyReader等,注解定义的Bean通过Scanner来找到,并且解析识别。
BeanDefination
最终识别为Bean的一个抽象定义BeanDefination。容器会维护相应的集合,记录所有的Bean的定义。
这就是Spring容器非常好的一个抽象设计,很值得我们借鉴学习。
整体可以可以概括如下图所示:
创建了容器,容器内部,有一些组件可以帮助容器识别到Bean,并且统一解析为BeanDefination。希望你可以领悟到这个,这个对你理解Spring容器非常关键的。
至于具体每一个Reader或Scanner如何通过ClassLoader扫描和查找资源的,创建容器这里还没有涉及,之后如果可以的话,大家再仔细分析这些组件就可以了。
最后提一点,创建容器后,有一段exceptionReporters的获取,这个从名字就可以猜出来,是处理异常统计和汇报的组件,不是很关键,我们抓大放小,过就可以了。
public ConfigurableApplicationContext run(String... args) {
//扩展点 SpringApplicationRunListeners listeners.starting();
//配置文件的处理和抽象封装 ConfigurableEnvironment
//容器相关处理,核心分析了它的核心组件脉络和抽象设计
context = createApplicationContext();
//处理异常统计和汇报的组件,不重要,抓大放小,过就可以了。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] {ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
refreshContext(context);
//其他逻辑
}
小结
今天我们主要分析了容器的创建。核心分析了它的核心组件脉络和抽象设计
1)核心组件Reader和Scanner,还有最关键的DefaultListableBeanFactory。并且熟悉了BeanDefinition、BeanDefinitionRegistry的概念。
2)容器关键的抽象设计,从Resource->ClassLoader->Reader/Scanner->BeanDefination。Spring通过这样的设计来获取到Bean对象的定义,最终帮我们管理对象。
下一节我们来一起看下有了容器对象,会为容器准备和设置哪些其他的内容,在这个过程中,容器又有会执行哪一些扩展点。