Spring Boot - IOC(一)
1. SpringFramework与SpringBoot的IOC
1.1 重新认识ApplicationContext
我们在初学 SpringFramework 的时候,你接触的第一样IOC容器一般都是 ClassPathXmlApplicationContext
,而且我们使用 ApplicationContext
来接收它。如下所示:
所以我们的一般认知中,ApplicationContext
是最顶级的IOC容器,那实际上是这样吗?
1.1.1 ApplicationContext并不是最顶级容器
翻看 ApplicationContext
的源码:
发现它继承了好多个接口!换句话说,他根本算不上最顶级的IOC容器。那最顶级的容器是什么呢?文档注释中没有很明确的表述,我们来翻看 SpringFramework 的官方文档:
第一章就叫The IoC Container ,在1.1节就已经明确的给出了答案:
The BeanFactory
interface provides an advanced configuration mechanism capable of managing any type of object.ApplicationContext
is a sub-interface of BeanFactory
.
BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext
是 BeanFactory
的子接口。
由此可见 BeanFactory
才是IOC容器最顶级的接口。
为什么SpringFramework建议使用 ApplicationContext
而不是 BeanFactory
,以至于我们一开始都不知道他呢?官方文档的1.16.1节有给出解释:
You should use an ApplicationContext
unless you have a good reason for not doing so, with GenericApplicationContext
and its subclass AnnotationConfigApplicationContext
as the common implementations for custom bootstrapping. These are the primary entry points to Spring’s core container for all common purposes: loading of configuration files, triggering a classpath scan, programmatically registering bean definitions and annotated classes, and (as of 5.0) registering functional bean definitions.
Because an ApplicationContext
includes all the functionality of a BeanFactory
, it is generally recommended over a plain BeanFactory
, except for scenarios where full control over bean processing is needed. Within an ApplicationContext
(such as the GenericApplicationContext
implementation), several kinds of beans are detected by convention (that is, by bean name or by bean type — in particular, post-processors), while a plain DefaultListableBeanFactory
is agnostic about any special beans.
For many extended container features, such as annotation processing and AOP proxying, the BeanPostProcessor
extension point is essential. If you use only a plain DefaultListableBeanFactory
, such post-processors do not get detected and activated by default. This situation could be confusing, because nothing is actually wrong with your bean configuration. Rather, in such a scenario, the container needs to be fully bootstrapped through additional setup.
除非有充分的理由,否则你应使用 ApplicationContext
,除非将 GenericApplicationContext
及其子类 AnnotationConfigApplicationContext
作为自定义引导的常见实现,否则应使用 ApplicationContext
。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件,触发类路径扫描,以编程方式注册Bean定义和带注解的类,以及(从5.0版本开始)注册功能性Bean定义。
因为 ApplicationContext
包含 BeanFactory
的所有功能,所以通常建议在纯 BeanFactory
上使用,除非需要对Bean处理的完全控制。在 ApplicationContext
(例如 GenericApplicationContext
实现)中,按照约定(即,按Bean名称或Bean类型(尤其是后处理器))检测到几种Bean,而普通的 DefaultListableBeanFactory
不知道任何特殊的Bean。
对于许多扩展的容器功能(例如注解处理和AOP代理),BeanPostProcessor
扩展点是必不可少的。如果仅使用普通的 DefaultListableBeanFactory
,则默认情况下不会检测到此类后处理器并将其激活。这种情况可能会造成混淆,因为您的bean配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。
文档已经描述的很清楚了,ApplicationContext
的功能更强大,所以选择用它。
1.1.2 ApplicationContext的接口继承
利用IDEA查看 ApplicationContext
接口的继承关系,我们只关注它与 BeanFactory
的关系:
它通过两个中间的接口,最终继承到 BeanFactory
中。那这两个接口分别又是什么呢?
1.1.2.1 ListableBeanFactory
它的文档注释原文翻译:
Extension of the BeanFactory
interface to be implemented by bean factories that can enumerate all their bean instances, rather than attempting bean lookup by name one by one as requested by clients. BeanFactory
implementations that preload all their bean definitions (such as XML-based factories) may implement this interface. If this is a HierarchicalBeanFactory
, the return values will not take any BeanFactory hierarchy into account, but will relate only to the beans defined in the current factory. Use the BeanFactoryUtils
helper class to consider beans in ancestor factories too. The methods in this interface will just respect bean definitions of this factory. They will ignore any singleton beans that have been registered by other means like org.springframework.beans.factory.config.ConfigurableBeanFactory
's registerSingleton
method, with the exception of getBeanNamesOfType
and getBeansOfType
which will check such manually registered singletons too. Of course, BeanFactory's getBean
does allow transparent access to such special beans as well. However, in typical scenarios, all beans will be defined by external bean definitions anyway, so most applications don't need to worry about this differentiation.
它是 BeanFactory
接口的扩展,它可以实现枚举其所有bean实例,而不是按客户的要求按名称一一尝试进行bean查找。预加载其所有bean定义的 BeanFactory
实现(例如,基于XML的工厂)可以实现此接口。
如果实现类同时也实现了 HierarchicalBeanFactory
,返回值也不会考虑任何 BeanFactory
层次结构,而仅与当前工厂中定义的bean有关。但可以使用 BeanFactoryUtils
工具类来获取父工厂中的bean。
该接口中的方法将仅遵守该工厂的bean定义。他们将忽略通过其他方式(例如 ConfigurableBeanFactory
的 registerSingleton
方法)注册的任何单例bean,但 getBeanNamesOfType
和 getBeansOfType
除外,它们也将检查此类手动注册的单例。当然,BeanFactory
的getBean
确实也允许透明访问此类特殊bean。但是,在典型情况下,无论如何,所有bean都将由外部bean定义来定义,因此大多数应用程序不必担心这种区别。
从文档注释中可以获取到的最重要的信息:它可以提供Bean的迭代!
1.1.2.2 HierarchicalBeanFactory
它的文档注释原文翻译:
Sub-interface implemented by bean factories that can be part of a hierarchy. The corresponding setParentBeanFactory
method for bean factories that allow setting the parent in a configurable fashion can be found in the ConfigurableBeanFactory
interface.
由Bean工厂实现的子接口,可以是层次结构的一部分。
可以在 ConfigurableBeanFactory
接口中找到用于bean工厂的相应 setParentBeanFactory
方法,该方法允许以可配置的方式设置父对象。
文档注释中写的比较模糊,但可以大概看出来它涉及到层次。这个接口有一个方法,可以彻底帮我们解决疑惑:
获取父工厂?我们在一开始学 SpringMVC 的时候了解到,在原生的Web开发中,配置 SpringFramework 和 SpringMVC,是需要配置父子容器的!
换言之,这个接口是实现多层嵌套容器的支撑。
1.1.3 ApplicationContext的其他特征
回到 ApplicationContext
的接口定义,它还继承了几个接口:
1.1.3.1 EnvironmentCapable
文档注释原文翻译:
Interface indicating a component that contains and exposes an Environment
reference.
实现了此接口的类有应该有一个 Environment
类型的域,并且可以通过 getEnvironment 方法取得。
这个接口只有一个方法:
发现是跟 Environment
相关的。这个 **Environment**
的概念非常重要,会在后续IOC容器的解析时起到很大作用,后面会详细解释。
1.1.3.2 MessageSource
翻看它的文档注释:
Strategy interface for resolving messages, with support for the parameterization and internationalization of such messages.
用于解析消息的策略接口,并支持此类消息的参数化和国际化。
很明显,它是实现国际化的接口。说明 ApplicationContext
还支持国际化。
1.1.3.3 ApplicationEventPublisher
字面意思都很容易理解:应用事件发布器。它的文档注释:
Interface that encapsulates event publication functionality.
封装事件发布功能的接口。
1.1.3.4 ResourcePatternResolver
字面意思也能理解:资源模式解析器。它的文档注释:
Strategy interface for resolving a location pattern (for example, an Ant-style path pattern) into Resource objects. This is an extension to the ResourceLoader interface. A passed-in ResourceLoader
(for example, an org.springframework.context.ApplicationContext
passed in via org.springframework.context.ResourceLoaderAware
when running in a context) can be checked whether it implements this extended interface too. PathMatchingResourcePatternResolver
is a standalone implementation that is usable outside an ApplicationContext, also used by ResourceArrayPropertyEditor
for populating Resource array bean properties. Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"
): Input patterns have to match the strategy implementation. This interface just specifies the conversion method rather than a specific pattern format. This interface also suggests a new resource prefix "classpath*:"
for all matching resources from the class path. Note that the resource location is expected to be a path without placeholders in this case (e.g. "/beans.xml"
); JAR files or classes directories can contain multiple files of the same name.
策略接口,用于将位置模式(例如,Ant样式的路径模式)解析为Resource
对象。
这是 ResourceLoader
接口的扩展。可以检查传入的 ResourceLoader
(例如,在上下文中运行时通过 ResourceLoaderAware
传入的 ApplicationContext
)是否也实现了此扩展接口。
PathMatchingResourcePatternResolver
是一个独立的实现,可在 ApplicationContext 外部使用,ResourceArrayPropertyEditor
也使用它来填充Resource数组Bean属性。
可以与任何类型的位置模式一起使用(例如 "/WEB-INF/*-context.xml"
):输入模式必须与策略实现相匹配。该接口仅指定转换方法,而不是特定的模式格式。 此接口还为类路径中的所有匹配资源建议一个新的资源前缀 "classpath*:"
。请注意,在这种情况下,资源位置应该是没有占位符的路径(例如 "/beans.xml"
); jar包或类目录可以包含多个相同名称的文件。
有过SSH/SSM整合的小伙伴,一定能很清晰的理解上面的意思。我们之前在web.xml中配置 ContextLoaderListener
,并且声明 contextConfigLocation
时,配置的参数值就是类似于上面的格式。
至此,ApplicationContext
的结构已经分析完毕,下面咱来看 ApplicationContext
的子接口和一些重要的实现类。
1.1.4 ConfigurableApplicationContext
很明显,它是一个可配置的 ApplicationContext
。它可配置在什么地方呢?我们来对比一下 ConfigurableApplicationContext
和 ApplicationContext
:
发现 ApplicationContext
中全部都是get方法,但在 ConfigurableApplicationContext
中开始出现了set方法。
ConfigurablApplicationContext
的文档注释原文翻译:
SPI interface to be implemented by most if not all application contexts. Provides facilities to configure an application context in addition to the application context client methods in the ApplicationContext interface. Configuration and lifecycle methods are encapsulated here to avoid making them obvious to ApplicationContext client code. The present methods should only be used by startup and shutdown code.
它是一种SPI接口,将由大多数(如果不是全部)ApplicationContext
的子类实现。除了 ApplicationContext
接口中的应用程序上下文客户端方法外,还提供了用于配置 ApplicationContext
的功能。
配置和生命周期方法都封装在这里,以避免这些代码显式的暴露给 ApplicationContext
客户端代码。本方法仅应由启动和关闭容器的代码使用。
其实这个接口是一个非常关键的核心接口。它包含了最核心的方法:refresh,它的作用会在后续IOC容器的启动刷新时详细解析。
1.1.5 AbstractApplicationContext
它是 ConfigurableApplicationContext
的第一级实现类,同时也是抽象类。它的文档注释原文翻译:
Abstract implementation of the ApplicationContext
interface. Doesn't mandate the type of storage used for configuration; simply implements common context functionality. Uses the Template Method design pattern, requiring concrete subclasses to implement abstract methods. In contrast to a plain BeanFactory
, an ApplicationContext
is supposed to detect special beans defined in its internal bean factory: Therefore, this class automatically registers BeanFactoryPostProcessors
, BeanPostProcessors
, and ApplicationListeners
which are defined as beans in the context. A MessageSource
may also be supplied as a bean in the context, with the name "messageSource"; otherwise, message resolution is delegated to the parent context. Furthermore, a multicaster for application events can be supplied as an "applicationEventMulticaster" bean of type ApplicationEventMulticaster
in the context; otherwise, a default multicaster of type SimpleApplicationEventMulticaster will be used. Implements resource loading by extending DefaultResourceLoader
. Consequently treats non-URL resource paths as class path resources (supporting full class path resource names that include the package path, e.g. "mypackage/myresource.dat"
), unless the getResourceByPath method is overridden in a subclass.
ApplicationContext
接口的抽象实现。不强制用于配置的存储类型;简单地实现通用上下文功能。这个类使用模板方法模式,需要具体的子类来实现抽象方法。
与普通 BeanFactory
相比,ApplicationContext
应该检测其内部bean工厂中定义的特殊bean:因此,此类自动注册在上下文中定义为bean的 BeanFactoryPostProcessors
,BeanPostProcessors
和 ApplicationListeners
。
一个 MessageSource
也可以在上下文中作为bean提供,名称为“messageSource”。否则,将消息解析委托给父上下文。此外,可以在上下文中将用于应用程序事件的广播器作为类型为 ApplicationEventMulticaster
的 "applicationEventMulticaster"
bean提供。否则,将使用类型为 SimpleApplicationEventMulticaster
的默认广播器。
通过扩展 DefaultResourceLoader
实现资源加载。因此,除非在子类中覆盖了 getResourceByPath 方法,否则将非URL资源路径视为类路径资源(支持包含包路径的完整类路径资源名称,例如 "mypackage / myresource.dat"
)。
从文档注释中可以看出,它已经实现了 ConfigurableApplicationContext
接口,但里面提供了几个模板方法,用于子类重写(多态)。
这个类的refresh方法是将来IOC容器启动刷新时要分析的核心方法,后续会详细解析。
1.1.6 常用的ApplicationContext的实现类
前面我们了解完 ApplicationContext
和 BeanFactory
的关系,对这个接口以及子接口、抽象实现类也有了一个最基本的认知。下面我们对 SpringFramework 中最常用的两个IOC容器实现类来简单介绍一下。
1.1.6.1 ClassPathXmlApplicationContext
我们都很熟悉,在一开始 SpringFramework 入门的时候就用过了。它的类定义和继承结构图:
在继承关系图中,可以发现 ClassPathXmlApplicationContext
的几个特征:基于XML,可刷新的,可配置的。
在 SpringFramework 的官方文档1.2节有介绍基础IOC容器的使用,这里面大量介绍了 ClassPathXmlApplicationContext,小册不作过多解释。
1.1.6.2 AnnotationConfigApplicationContext
我们在一开始学习启动原理时也用过了,它是使用注解配置来加载初始化IOC容器的。它的类定义和继承结构图:
它的继承关系相对简单,而且还实现了 Annotation 相关的接口。
在 SpringFramework 的官方文档1.12节,有专门的基于Java配置的容器的介绍。
它配合的注解咱也见过不少了。由于小册主要讲解原理和源码,对于这些容器和注解的基本使用不作过多介绍。
1.2 SpringBoot对IOC容器的扩展
【该部分只作为前置知识,可以先不了解】
在spring-boot的jar包中,org.springframework.boot.web
路径下有一个 context 包,里面有两个接口:WebServerApplicationContext
、ConfigurableWebServerApplicationContext
。
翻看 WebServerApplicationContext
的接口定义:
发现它直接继承了 ApplicationContext
,说明它与上面的提到的 ApplicationContext
的子接口都没关系了,这是独成一套。它的文档注释原文翻译:
Interface to be implemented by application contexts that create and manage the lifecycle of an embedded WebServer
.
由创建和管理嵌入式Web服务器的生命周期的应用程序上下文实现的接口。
它与嵌入式Web服务器有关系。而我们之前学习 SpringBoot 的时候,就已经了解 SpringBoot 的一大优势就是嵌入式Web服务器。它在后续的IOC容器启动时也会有相关介绍。
利用IDEA查看这个接口的子接口和实现类:
发现一共有5个实现类,一个子接口(恰好就是之前看到的 ConfigurableWebServerApplicationContext
)。这里面的 ApplicationContext 会在后续分析启动过程时会遇见,此处仅做接触了解。
2. SpringBoot准备IOC容器
2.1 main方法进入
从最简单的入门程序开始:
2.2 进入SpringApplication.run方法
进入run方法,可以发现执行的 SpringBoot 应用启动操作分为两步:
run方法返回的是 ApplicationContext
的子接口:ConfigurableApplicationContext
,之前我们已经了解过了,不再赘述。
底下的run方法分为两步,分开来看:
2.3 new SpringApplication(primarySources):创建SpringApplication
最终调用的构造方法是下面的两参数方法。
暂且不看这个方法的具体实现,先看一眼构造方法的文档注释:
Create a new SpringApplication
instance. The application context will load beans from the specified primary sources (see class-level documentation for details. The instance can be customized before calling run(String...)
.
创建一个新的 SpringApplication
实例。应用程序上下文将从指定的主要源加载Bean(有关详细信息,请参见类级别的文档)。可以在调用run(String...)之前自定义实例。
文档中描述可以在run方法之前自定义实例,换句话说,可以手动配置一些 SpringApplication 的属性。
【如果小伙伴没有见过自定义配置 SpringApplication
,请继续往下看;了解的小伙伴请跳过3.0节】
2.3.0 自定义SpringApplication
下面对 SpringApplication 的构造方法实现中每一步作详细解析:
2.3.1 WebApplicationType.deduceFromClasspath:判断当前应用环境
这个方法没有文档注释,但方法名和返回值类型已经可以描述方法用途:从classpath下判断当前SpringBoot应用应该使用哪种环境启动。
上面的代码块中我把一些这个类中定义的常量也贴了进来,方便小伙伴们阅读。它们是描述了一些 Servlet
的全限定名、DispatcherServlet
的全限定名等等,它们的用途是配合下面的方法判断应用的classpath里是否有这些类。
下面的方法实现中:
第一个if结构先判断是否是 Reactive 环境,发现有 WebFlux 的类但没有 WebMvc 的类,则判定为 Reactive 环境(全NIO)
之后的for循环要检查是否有跟 Servlet 相关的类,如果有任何一个类没有,则判定为非Web环境
如果for循环走完了,证明所有类均在当前 classpath 下,则为 Servlet(WebMvc) 环境
2.3.2 setInitializers:设置初始化器
setInitializers方法会将一组类型为 ApplicationContextInitializer
的初始化器放入 SpringApplication
中。
而这组 **ApplicationContextInitializer**
,是在构造方法中,通过 getSpringFactoriesInstances
得到的。
在阅读这部分源码之前,先来了解一下 ApplicationContextInitializer
是什么。
2.3.2.0 【重要】ApplicationContextInitializer
文档注释原文翻译:
Callback interface for initializing a Spring ConfigurableApplicationContext prior to being refreshed. Typically used within web applications that require some programmatic initialization of the application context. For example, registering property sources or activating profiles against the context's environment. See ContextLoader and FrameworkServlet support for declaring a "contextInitializerClasses" context-param and init-param, respectively. ApplicationContextInitializer processors are encouraged to detect whether Spring's Ordered interface has been implemented or if the @Order annotation is present and to sort instances accordingly if so prior to invocation.
用于在刷新容器之前初始化Spring ConfigurableApplicationContext
的回调接口。
通常在需要对应用程序上下文进行某些编程初始化的Web应用程序中使用。例如,根据上下文环境注册属性源或激活配置文件。请参阅 ContextLoader
和FrameworkServlet
支持,分别声明 "contextInitializerClasses"
的 context-param 和 init-param。
鼓励 ApplicationContextInitializer
处理器检测是否已实现Spring的 Ordered
接口,或者是否标注了 @Order
注解,并在调用之前相应地对实例进行排序。
第一句注释已经解释的很明白了,它是在IOC容器之前的回调。它的使用方式有三种:
2.3.2.0.1 运行SpringApplication之前手动添加
先编写一个Demo:
之后在主启动类上手动添加:
运行主启动类,控制台打印(看Banner下面的第一行):
2.3.2.0.2 application.properties中配置
在 application.properties
中配置如下内容:
2.3.2.0.3 spring.factories中配置
在工程的 resources 目录下新建 “META-INF” 目录,并在下面创建一个 spring.factories
文件。在文件内声明:
三种方式效果都是一样的。
回到上面的方法中:
方法中有两步是比较重要的,下面分别来看:
2.3.2.1 SpringFactoriesLoader.loadFactoryNames
这个方法我们已经在之前详细解析过,这里不重复解释,不过我们可以看一眼 spring-boot
和 spring-boot-autoconfigure
包下的 spring.factories
里面对于 ApplicationContextInitializer
的配置:
它一共配置了6个 ApplicationContextInitializer
,对这些Initializer作简单介绍:
ConfigurationWarningsApplicationContextInitializer
:报告IOC容器的一些常见的错误配置ContextIdApplicationContextInitializer
:设置Spring应用上下文的IDDelegatingApplicationContextInitializer
:加载application.properties
中context.initializer.classes
配置的类ServerPortInfoApplicationContextInitializer
:将内置servlet容器实际使用的监听端口写入到Environment
环境属性中SharedMetadataReaderFactoryContextInitializer
:创建一个 SpringBoot 和ConfigurationClassPostProcessor
共用的CachingMetadataReaderFactory
对象ConditionEvaluationReportLoggingListener
:将ConditionEvaluationReport
写入日志
2.3.2.2 createSpringFactoriesInstances:反射创建这些组件的实例
2.3.3 setListeners:设置监听器
与上面一样,先了解下 ApplicationListener
:
2.3.3.0 【重要】ApplicationListener
它的文档注释原文翻译:
Interface to be implemented by application event listeners. Based on the standard java.util.EventListener interface for the Observer design pattern. As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
由应用程序事件监听器实现的接口。基于观察者模式的标准 java.util.EventListener
接口。
从Spring 3.0开始,ApplicationListener
可以一般性地声明监听的事件类型。向IOC容器注册后,将相应地过滤事件,并且仅针对匹配事件对象调用监听器。
文档注释也写的很明白,它就是监听器,用于监听IOC容器中发布的各种事件。至于事件是干嘛的,要到后续看IOC容器的刷新过程时才能看到。
2.3.3.1 加载Listener
套路与 setInitializers
一致,同样的我们来看看它加载了的 Listener:
ClearCachesApplicationListener
:应用上下文加载完成后对缓存做清除工作ParentContextCloserApplicationListener
:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播FileEncodingApplicationListener
:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动AnsiOutputApplicationListener
:根据spring.output.ansi.enabled
参数配置 AnsiOutputConfigFileApplicationListener
:从常见的那些约定的位置读取配置文件DelegatingApplicationListener
:监听到事件后转发给application.properties
中配置的context.listener.classes
的监听器ClasspathLoggingApplicationListener
:对环境就绪事件ApplicationEnvironmentPreparedEvent
和应用失败事件ApplicationFailedEvent
做出响应LoggingApplicationListener
:配置LoggingSystem
。使用logging.config
环境变量指定的配置或者缺省配置LiquibaseServiceLocatorApplicationListener
:使用一个可以和 SpringBoot 可执行jar包配合工作的版本替换 LiquibaseServiceLocatorBackgroundPreinitializer
:使用一个后台线程尽早触发一些耗时的初始化任务
2.3.4 deduceMainApplicationClass:确定主配置类
源码很简单,从 deduceMainApplicationClass
方法开始往上爬,哪一层调用栈上有main方法,方法对应的类就是主配置类,就返回这个类。
实际上通过Debug可以发现,发现这部分的 stackTrace 就是调用栈:
那自然最下面调用的方法是main方法,由此可确定主配置类。
2.3.5 【补充】与SpringBoot1.x的区别
3. 准备运行时环境
new SpringApplication()
完成后,下面开始执行run方法:
在开始走 run 方法之前,咱先大体对这部分有一个宏观的认识,便于咱接下来理解。
3.1 run():启动SpringApplication
源码很长,这里我们拆成几篇来看,本篇先来看前置准备和运行时环境的准备。
3.1.1 new StopWatch():创建StopWatch对象
这个组件看上去貌似跟时间相关,看它的文档注释(最后一句):
This class is normally used to verify performance during proof-of-concepts and in development, rather than as part of production applications.
常用于在概念验证和开发过程中验证性能,而不是作为生产应用程序的一部分。
注释已经解释的很明确了:仅用于验证性能。也就是说,这个组件是用来监控启动时间的,不是很重要,我们不作深入研究。看一眼源码吧:
3.1.2 创建空的IOC容器,和一组异常报告器
这段源码本身非常简单,但 SpringBootExceptionReporter
是什么呢?
3.1.2.0 SpringBootExceptionReporter
这个接口是SpringBoot2.0出现的,它是一种异常分析器。它的文档注释原文翻译:
Callback interface used to support custom reporting of SpringApplication
startup errors. reporters are loaded via the SpringFactoriesLoader
and must declare a public constructor with a single ConfigurableApplicationContext
parameter.
用于支持 SpringApplication
启动错误报告的自定义报告的回调接口,它通过 SpringFactoriesLoader
加载,并且必须使用单个 ConfigurableApplicationContext
参数声明公共构造函数。
文档注释已经写明白了,它是启动错误报告的报告器,并且也是用 SpringFactoriesLoader
加载。通过使用IDEA的实现继承关系查看,发现它的实现类只有一个: FailureAnalyzers
。
3.1.2.1 与SpringBoot1.x的对比
SpringBoot1.x中声明的直接是 FailureAnalyzers
,而且是一个。
3.1.3 configureHeadlessProperty:设置awt相关
这段源码很诡异,它从 System
中取了一个配置,又给设置回去了。这样做的目的是什么呢?
这就要看jdk中 System
类的这两个方法了:
发现 System
类中有两个重载的 getProperty
方法,但只有一个 setProperty
!仔细观察源码,发现重载的方法有一点微妙的区别。这里要提一下 Properties
的机制:
setProperty
方法中调用的是 Properties
的两参数 setProperty
方法,分别代表key和value,这自然不必多说。getProperty
方法的两个重载的方法唯一的区别是调用 Properties
的一参数和两参数方法,它的区别类似于Map中的get
和getOrDefault
。换句话说,getProperty
的两参数方法如果取不到指定的key,则会返回一个默认值;一个参数的方法调用时没有则返回null。
经过上述源码的设置后,这样无论如何都能取到这个key为 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS
的value了。那这个 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS
又是什么呢?
由此可得,这段源码的真正作用是:设置应用在启动时,即使没有检测到显示器也允许其继续启动。(服务器嘛,没显示器照样得运行。。。)
3.1.4 getRunListeners:获取SpringApplicationRunListeners
加载机制我们懂,那 SpringApplicationRunListeners
是什么呢?
3.1.4.0 【重要】SpringApplicationRunListeners
文档注释已在源码中直接标注,不再拆分到正文。
值得注意的是,started、running、failed方法是 SpringBoot2.0 才加入的。
后续这个run方法中会常出现这些 SpringApplicationRunListeners
的身影,我会特别标注出来的,小伙伴们也多加留意。
通过Debug,发现默认情况下加载的listeners有一个,类型为 EventPublishingRunListener
:
回到 run 方法中:
在 prepareEnvironment
之前,run方法中调用了:listeners.starting()
,已经开始了事件回调。
接下来要执行的方法:prepareEnvironment
3.1.5 prepareEnvironment:准备运行时环境
暂且不看方法实现,先了解一下 Environment
是什么东西(之前说过 Environment
非常重要)。
【如果小伙伴不了解 Environment
,请继续往下看;熟悉的小伙伴请直接跳过4.5.0节】
3.1.5.0 【重要】Environment与ConfigurableEnvironment
3.1.5.0.1 Environment
它的文档注释非常长:
Interface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the PropertyResolver
superinterface. A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema
or the @Profile
annotation for syntax details. The role of the Environment
object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default. Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them. Beans managed within an ApplicationContext
may register to be EnvironmentAware
or @Inject
the Environment in order to query profile state or resolve properties directly. In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer
, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using <context:property-placeholder/>
. Configuration of the environment object must be done through the ConfigurableEnvironment
interface, returned from all AbstractApplicationContext
subclass getEnvironment()
methods. See ConfigurableEnvironment Javadoc
for usage examples demonstrating manipulation of property sources prior to application context refresh()
.
表示当前应用程序正在其中运行的环境的接口。它为应用环境制定了两个关键的方面:profile 和 properties。与属性访问有关的方法通过 PropertyResolver
这个父接口公开。
profile机制保证了仅在给定 profile 处于激活状态时,才向容器注册的Bean定义的命名逻辑组。无论是用XML定义还是通过注解定义,都可以将Bean分配给指定的 profile。有关语法的详细信息,请参见spring-beans 3.1规范文档
或 @Profile
注解。Environment 的作用是决定当前哪些配置文件(如果有)处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。
Properties
在几乎所有应用程序中都起着重要作用,并且可能来源自多种途径:属性文件,JVM系统属性,系统环境变量,JNDI,ServletContext 参数,临时属性对象,Map等。Environment
与 Properties
的关系是为用户提供方便的服务接口,以配置属性源,并从中解析属性值。
在 ApplicationContext
中管理的Bean可以注册为 EnvironmentAware
或使用 @Inject
标注在 Environment 上,以便直接查询profile的状态或解析 Properties
。
但是,在大多数情况下,应用程序级Bean不必直接与 Environment 交互,而是通过将${...}属性值替换为属性占位符配置器进行属性注入(例如 PropertySourcesPlaceholderConfigurer
),该属性本身是 EnvironmentAware
,当配置了 <context:property-placeholder/>
时,默认情况下会使用Spring 3.1的规范注册。
必须通过从所有 AbstractApplicationContext
子类的 getEnvironment()
方法返回的 ConfigurableEnvironment
接口完成环境对象的配置。请参阅 ConfigurableEnvironment
的Javadoc以获取使用示例,这些示例演示在应用程序上下文 refresh()
方法被调用之前对属性源进行的操作。
简单概括一下:它是IOC容器的运行环境,它包括Profile和Properties两大部分,它可由一个到几个激活的Profile共同配置,它的配置可在应用级Bean中获取。
可以这样理解:
3.1.5.0.2 ConfigurableEnvironment
它的文档注释更长,我们只摘选最核心的部分,举例部分不作阅读:
Configuration interface to be implemented by most if not all Environment types. Provides facilities for setting active and default profiles and manipulating underlying property sources. Allows clients to set and validate required properties, customize the conversion service and more through the ConfigurablePropertyResolver
superinterface.
大多数(如果不是全部)Environment
类型的类都将实现的配置接口。提供用于设置 Profile 和默认配置文件以及操纵基础属性源的工具。允许客户端通过ConfigurablePropertyResolver
根接口设置和验证所需的属性、自定义转换服务以及其他功能。
从文档注释中发现这种机制与 ApplicationContext
、ConfigurableApplicationContext
类似,都是一个只提供get,另一个扩展的提供set。具体源码小册不再贴出,小伙伴们可以借助IDE自行查看。
回到方法实现:
3.1.5.1 getOrCreateEnvironment:创建运行时环境
源码很简单,还是根据当前的应用运行环境类型,创建不同的 Environment
。默认 SpringBoot 环境下会创建 StandardServletEnvironment
。
3.1.5.2 configureEnvironment:配置运行时环境
前面的if结构是向 Environment
中添加一个 ConversionService
。至于 ConversionService
是什么,把这个方法先大概看一遍再了解。
添加完 ConversionService
之后,要分别配置 PropertySource 和 Profiles,底层比较简单且一般情况不会执行,不再展开描述。
3.1.5.2.1 ConversionService是什么
文档注释原文翻译:
A service interface for type conversion. This is the entry point into the convert system. Call convert(Object, Class)
to perform a thread-safe type conversion using this system.
用于类型转换的服务接口。这是转换系统的入口,调用 convert(Object, Class)
使用此系统执行线程安全的类型转换。
可以看出它是一个类型转换的根接口。利用IDEA查看它的实现类,发现有一个实现类叫 DefaultConversionService
。不出意外的话,它至少能把这个接口所要描述的方法能实现了。
翻看 DefaultConversionService
的源码,发现里面有好多的 addXXXConverters
的方法。而这里面不乏有一些我们看上去比较熟悉的也比较容易猜测的:
StringToNumberConverterFactory
StringToBooleanConverter
IntegerToEnumConverterFactory
ArrayToCollectionConverter
StringToArrayConverter
......
果然它能做得类型转换还不少。实际上就是它在 SpringWebMvc 中做参数类型转换。
3.1.5.3 bindToSpringApplication:环境与应用绑定
这里面的核心源码就一句话,Binder
的 bind 方法:
手头有IDE的小伙伴,当你再点开下面bind方法的时候,你的心情可能跟我一样是非常复杂的。随着调用的方法一层一层深入,可以发现这部分非常复杂。这里我们不作过多深入的探究,仅从方法的文档注释上来看它的解释:
Bind the specified target Bindable using this binder's property sources.
使用此绑定器的属性源,绑定指定的 可绑定的目标。
说白了,也就是把配置内容绑定到指定的属性配置类中(类似于 @ConfigurationProperties
)。
3.1.6 configureIgnoreBeanInfo:设置系统参数
它提到了一个配置:spring.beaninfo.ignore
,我们没见过他也没配置过,那就只好借助文档注释。在文档注释中有一句话比较关键:
"spring.beaninfo.ignore", with a value of "true" skipping the search for BeanInfo classes (typically for scenarios where no such classes are being defined for beans in the application in the first place).
"spring.beaninfo.ignore"
的值为“true”,则跳过对BeanInfo类的搜索(通常用于未定义此类的情况)首先是应用中的bean)。
由此可知,它是控制是否跳过 BeanInfo
类的搜索,并且由源码可知默认值是true,不作过多研究。
3.1.7 printBanner:打印Banner
在阅读这部分源码之前,先看看 Banner
到底是什么。
【如果小伙伴仅仅是知道 Banner
,不妨继续往下看看。对 Banner
很熟悉的小伙伴可以跳过4.7.0节】
3.1.7.0 Banner和它的实现类
它是一个接口,并且内置了一个枚举类型,代表 Banner 输出的模式(关闭、控制台打印、日志输出)。
借助IDEA,发现它有几个实现类:
第一眼应该关注的就是这个 SpringBootBanner
,我看它最熟悉。翻看它的源码:
看到了上面一堆奇怪但又有些熟悉的东西,常量名是 BANNER
,它就是在默认情况下打印在控制台的 Banner
。
它重写的 printBanner
方法,就是拿输出对象,把定义好的 Banner 和 SpringBoot 的版本号打印出去,逻辑比较简单。
回到 printBanner
中,看它的源码:
首先判断当前是否关闭了 Banner 输出,而默认值是打在控制台上。
之后要获取 ResourceLoader
,它的作用大概可以猜测到是加载资源的。
3.1.7.1 ResourceLoader
它的文档注释原文翻译:
Strategy interface for loading resources (e.. class path or file system resources).
用于加载资源(例如类路径或文件系统资源)的策略接口。
从接口定义和文档注释,已经基本证实了我们的猜测是正确的。上面方法默认创建的是 DefaultResourceLoader
,看它的 getResource
方法:
前面的实现都可以不看,注意最后的try块中,它借助URL类来加载资源,这种方式已经跟 classLoader 的方式差不太多了,至此石锤我们的猜测是正确的。
回到 printBanner
中:
获取到 ResourceLoader
后,下面要创建一个 SpringApplicationBannerPrinter
,默认情况下最终调用到最后的return中。
3.1.7.2 SpringApplicationBannerPrinter#print
首先它要获取 Banner
,之后打印 Banner
,最后把 Banner
封装成 PrintedBanner
返回。
3.1.7.2.1 getBanner:获取Banner
很明显它要先试着找有没有 图片Banner
和 文字Banner
,如果都没有,则会取默认的 Banner,而这个 Banner 恰好就是一开始看到的,也是我们最熟悉的 Banner。
以获取 文字Banner 为例,看看它是怎么拿的:
3.1.7.2.2 getTextBanner
首先它要看你有没有显式的在 application.properties
中配置 spring.banner.location
这个属性,如果有,就加载它,否则加载默认的位置,叫 banner.txt
。
由此可见 SpringBoot 的设计原则:约定大于配置。
拿到 Banner 后,打印,返回,Banner 部分结束。
3.1.8 createApplicationContext:创建IOC容器
可以发现都是创建的基于Annotation的 ApplicationContext。
(如果是非Web环境,创建的 ApplicationContext
与常规用 SpringFramework 时使用的注解驱动IOC容器一致)
注意 BeanFactory
在这里已经被创建了:
之前分析过,默认导入 spring-boot-start-web
时,Servlet环境生效,故上面导入的类为:AnnotationConfigServletWebServerApplicationContext
。这个类将在后续的分析中大量出现。
到这里,咱对三种类型的运行时环境、IOC容器的类型归纳一下:
Servlet -
StandardServletEnvironment
-AnnotationConfigServletWebServerApplicationContext
Reactive -
StandardReactiveWebEnvironment
-AnnotationConfigReactiveWebServerApplicationContext
None -
StandardEnvironment
-AnnotationConfigApplicationContext
3.1.9 prepareContext:初始化IOC容器
先大体浏览这部分源码的内容,其中几个复杂的地方我们单独来看。
3.1.9.1 postProcessApplicationContext:IOC容器的后置处理
它设置了几个组件:
如果
beanNameGenerator
不为空,则把它注册到IOC容器中。BeanNameGenerator
是Bean的name生成器,指定的CONFIGURATION_BEAN_NAME_GENERATOR
在修改首字母大写后无法从IDEA索引到,暂且放置一边。ResourceLoader
和ClassLoader
,这些都在前面准备好了ConversionService
,用于类型转换的工具,前面也准备好了,并且还做了容器共享
3.1.9.2 applyInitializers:执行Initializer
这个方法会获取到所有 Initializer,调用initialize方法。而这些 Initializer,其实就是刚创建 SpringApplication
时准备的那些 ApplicationContextInitializer
。
通过Debug发现默认情况下确实是那6个 Initializer :
来到 prepareContext 的最后几行:
3.1.9.3 getAllSources
它要加载 primarySources 和 sources 。
在之前分析的时候, primarySources 已经被设置过了,就是主启动类。sources 不清楚也没见过,通过Debug发现它确实为空。
也就是说,getAllSources 实际上是把主启动类加载进来了。
加载进来之后,就要注册进去,来到load方法:
3.1.9.4 【复杂】load
在调用 createBeanDefinitionLoader
方法之前,它先获取了 BeanDefinitionRegistry
。
3.1.9.4.1 getBeanDefinitionRegistry
发现它在拿IOC容器进行类型判断和强转。
前面分析了,我们最终拿到的IOC容器是 AnnotationConfigServletWebServerApplicationContext
,它的类继承结构:
它继承自 GenericApplicationContext
,而 GenericApplicationContext
就继承了 AbstractApplicationContext
,实现了 BeanDefinitionRegistry
接口。
所以上面的源码实际上把IOC容器返回去了。
3.1.9.4.2 createBeanDefinitionLoader
拿到IOC容器后,进入 createBeanDefinitionLoader
方法:
源码非常简单,直接new了一个 BeanDefinitionLoader
。那 BeanDefinitionLoader
的构造方法都干了什么呢?
这里面发现了几个关键的组件:AnnotatedBeanDefinitionReader
(注解驱动的Bean定义解析器)、XmlBeanDefinitionReader
(Xml定义的Bean定义解析器)、ClassPathBeanDefinitionScanner
(类路径下的Bean定义扫描器),还有一个我们不用的 GroovyBeanDefinitionReader
(它需要经过isGroovyPresent方法,而这个方法需要判断classpath下是否有 groovy.lang.MetaClass
类)。
BeanDefinitionLoader
的文档注释原文翻译:
Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a simple facade over AnnotatedBeanDefinitionReader, XmlBeanDefinitionReader and ClassPathBeanDefinitionScanner.
从基础源(包括XML和JavaConfig)加载bean定义。充当 AnnotatedBeanDefinitionReader
,XmlBeanDefinitionReader
和 ClassPathBeanDefinitionScanner
的简单外观(整合,外观模式)。
正好呼应了上面的组件,而且它使用了外观模式,将这几个组件整合了起来。
3.1.9.4.3 load
创建好解析器后,在上面的源码中,它又往loader中设置了 beanNameGenerator、resourceLoader、environment,最后调用了它的load方法。
它拿到所有的 sources(其实就主启动类一个),继续调用重载的load方法:
它会根据传入的 source 的类型,来决定用哪种方式加载。主启动类属于 Class
类型,于是继续调用重载的方法:
上面的 Groovy 相关的我们不关心,下面它要检测是否为一个 Component。
回想主启动类,它被 @SpringBootApplication
注解标注,而 @SpringBootApplication
组合了一个 @SpringBootConfiguration
,它又组合了一个 @Configuration
注解,@Configuration
的底层就是一个 @Component
。
所以主启动类是一个 Component,进入 annotatedReader
的 register
方法中。
3.1.9.4.4 annotatedReader.register
【规律总结】SpringFramework 和 SpringBoot中 有很多类似于 xxx 方法和 doXXX 方法。一般情况下,xxx方法负责引导到 doXXX 方法,doXXX 方法负责真正的逻辑和工作。
进入 doRegisterBean
中:
3.1.9.4.5 doRegisterBean
(源码较长,不太复杂的部分直接在源码上标注单行注释了)
其中 AnnotationConfigUtils.processCommonDefinitionAnnotations
的实现:
原来这部分是在解析一些咱之前学习 SpringFramework 时候接触的注解啊!
最终会将这个 BeanDefinition 注册到IOC容器中,调用 BeanDefinitionReaderUtils
的 registerBeanDefinition
方法。
3.1.9.4.6 BeanDefinitionReaderUtils.registerBeanDefinition
第7行,看到了 registry.registerBeanDefinition
,有没有想起来之前在之前介绍手动装配时的 ImportBeanDefinitionRegistrar
?(第5篇 自动装配原理中,4.1.4小节)
在这里它就是这么把Bean的定义信息注册进IOC容器的。其中,Bean的名称和别名在这个方法也被分开处理。
看到这里,小伙伴们可能会疑惑,这个 BeanDefinition 是个什么东西,它在IOC容器中起到了什么作用呢?
3.1.9.5 【重要】BeanDefinition
它的文档注释原文翻译:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations. This is just a minimal interface: The main intention is to allow a BeanFactoryPostProcessor such as PropertyPlaceholderConfigurer to introspect and modify property values and other bean metadata.
BeanDefinition
描述了一个bean实例,该实例具有属性值,构造函数参数值以及具体实现所提供的更多信息。
这只是一个最小的接口:主要目的是允许 BeanFactoryPostProcessor
(例如 PropertyPlaceholderConfigurer
)内省和修改属性值和其他bean元数据。
从文档注释中可以看出,它是描述Bean的实例的一个定义信息,但它不是真正的Bean。这个接口还定义了很多方法,不一一列举,全部的方法定义小伙伴们可以自行从IDE中了解。
String getBeanClassName();
String getScope();
String[] getDependsOn();
String getInitMethodName();
boolean isSingleton();
.....
SpringFramework
设计的这种机制会在后续的Bean加载和创建时起到非常关键的作用,小伙伴们一定要留意。
下面提到了一个 BeanPostProcessor
,这个概念我们到下一篇再了解。
最后更新于