注意: 文章相对较长,可能会消耗您
60
分钟的时间。未经作者允许,禁止转裁。
Spring Boot
作为当前最流行的开发框架,使用起来相当便利,因为其自身已为我们做了太多的事情,站在了巨人肩上,我们的开发也变得相当快速有效。
对于Spring Boot
的简介就直接略过了,现在以最简单的一个WEB
工程来了解一下Spring Boot
的启动过程。
我们已当前(2019-11-01)最新的spring boot (v2.2.0.RELEASE)来创建工程, 对于一个一分钟可搭建的工程,就只包含以下几个重要文件。
pom.xml
在选择工程拥有的能力时,只选择了一项spring-boot-starter-web
, 其它都自动生成了。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.refiny</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
DemoApplication
命名的时候,我使用的是demo
,因此生成了DemoApplication
.
package com.refiny.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
DemoController
创建一个com.refiny.demo.controller
的包, 写一个方法在里面。
package com.refiny.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/demo")
public String demo(){
return "Hello World";
}
}
整个工程里面唯一写的代码就在这里了.
我们将配置文件做一些调整, 将application.properties
改成application.yml
. 并创建一个新的application-pro.yml
.
application.yml的文件内容是:
server:
port: 9877
spring:
profiles:
active: pro
application-pro.yml的文件内容是:
server:
port: 9876
完成之后,启动一下工程,通过页面访问http://localhost:9876/demo
就可以看到Hello World
的输出结果。
现在就基于这些代码,来从头看看是如何一步一步加载的。
spring boot推荐使用jar包的方式启动工程, 因此,启动命令通常形似java -jar demo.jar
,然后通过main
方法作为入口开始工程的启动, SpringApplication.run(DemoApplication.class, args)
将会执行初始化。
点击进入run
方法之后,连续的几个run
方法会直接把你带到最终的run
方法执行的位置,先不要急,这中间还有一步是new SpringApplication(primarySources).run(args)
, 这里生成了一个SpringApplication
的实例,再用这个实例去执行的下一步run
。
有两行代码是这样的:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
嗯,这里是要开始创建spring的工厂实例和监听器了。
看看getSpringFactoriesInstances
干了啥呢, 从SpringFactoriesLoader
中loadFactoryNames
, 在加载不同的工厂名时,还区分了类加载器,加载一次之后,就存入了缓存中,以后根据类加载器就直接拿出值来返回,第一次加载的时候,是读取的类路下的META-INF/spring.factories
这个文件, 在我们自定义自动配置文件的时候,也是使用的这个文件名, 是在这里用到的, 在这个只有WEB
能力的工程下, 加载了三个jar包里的META-INF/spring.factories
, 它们分别是:
如果你还从来都没有打开看过这个文件的话,我列一下spring-boot-2.2.0.RELEASE.jar!/META-INF/spring.factories
的内容在下面:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertiesFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
将这些工厂名都找到之后,就需要将这些工厂实例都创建出来,createSpringFactoriesInstances
就是创建工厂实例并加入列表中, 这里就是使用的BeanUtils
来反射创建的实例。
使用同样的方式ApplicationContextInitializer
和ApplicationListener
的相关实例都会被创建进去。
有了一上步的一些初始化动作, 现在再来看如何将这个工程run
起来。
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
1 StopWatch stopWatch = new StopWatch();
2 stopWatch.start();
3 ConfigurableApplicationContext context = null;
4 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
5 configureHeadlessProperty();
6 SpringApplicationRunListeners listeners = getRunListeners(args);
7 listeners.starting();
8 try {
9 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
10 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
11 configureIgnoreBeanInfo(environment);
12 Banner printedBanner = printBanner(environment);
13 context = createApplicationContext();
14 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
15 new Class[] { ConfigurableApplicationContext.class }, context);
16 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
17 refreshContext(context);
18 afterRefresh(context, applicationArguments);
19 stopWatch.stop();
20 if (this.logStartupInfo) {
21 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
22 }
23 listeners.started(context);
24 callRunners(context, applicationArguments);
25 }
26 catch (Throwable ex) {
27 handleRunFailure(context, ex, exceptionReporters, listeners);
28 throw new IllegalStateException(ex);
29 }
30 try {
31 listeners.running(context);
32 }
33 catch (Throwable ex) {
34 handleRunFailure(context, ex, exceptionReporters, null);
35 throw new IllegalStateException(ex);
36 }
37 return context;
}
前面几行都只是启动一个定时器用于计算整个启动过程花了多长时间的,第6
行又是获取一个监听器列表, 进入方法查看一下,也是调用的getSpringFactoriesInstances
, 直接到META-INF/spring.factories
中就可以看到SpringApplicationRunListener
的监听器就只有一个org.springframework.boot.context.event.EventPublishingRunListener
, 但这里不要误认为只能有一个呦,starting
里的代码是这样的:
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
因此是有多少监听器就启动多少个监听器。
这里的EventPublishingRunListener
就是在应用启动过程中发布事件使用的, starting
之后,就会有一个ApplicationStartingEvent
事件发布出来, 后续过程中将会有更多的发布事件出来。
第10
行代码开准备环境, 当前Spring Boot
工程的类型是是在SpringApplication
初始化的时候,就通过WebApplicationType.deduceFromClasspath()
确定了的, 为WebApplicationType.SERVLET
. 在getOrCreateEnvironment
中根据这个类型拿到一个new StandardServletEnvironment()
的实例。
在environment
中, 设置了一些参数,但都是一些初始的参数,例如ActiveProfiles
, 现在还并没有去读取application.yml
或application.properties
, 所以,也并没有把spring.profiles.active
设置的值读到。
环境有了之后, 监听器发布了一个事件listeners.environmentPrepared(environment);
, 由于当前只有一个EventPublishingRunListener
, 在environmentPrepared
中发布了ApplicationEnvironmentPreparedEvent
:
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
这个事件发布出来之后,你也可以根据这个事件对environment
做一些自己的事情, 但我们分析到这里,还没有出现容器和bean的初始化, 因此不能直接通过注解的方式来完成对这个事件的接收处理,我们可以如下完成这个事情:
写一个事件监听器:
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
System.out.println("------------------------------<接收到了ApplicationEnvironmentPreparedEvent>--------------------------------");
}
}
这个事件监听器只对ApplicationEnvironmentPreparedEvent
有效, 然后把这个监听器添加到SpringApplication
中:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
springApplication.addListeners(new ApplicationEnvironmentPreparedEventListener());
springApplication.run(args);
}
}
还有另一种写法,即写到/META-INF/spring.factories
下面, 在工程的resources
下创建这个文件, 然后把类名全称写进去:
# My Listeners
org.springframework.context.ApplicationListener=\
com.refiny.demo.listener.ApplicationEnvironmentPreparedEventListener
以后自己定义starter
和Auto Configuration
的时候也会是这种写法。
既然ApplicationEnvironmentPreparedEvent
的事件已经发了出来,有哪些监听这个事件的对象会做出响应呢? 在/META-INF/spring.factories
中就定义了一堆将用于事件的监听器,在EventPublishingRunListener
实例化的时候,就被加了进来:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
在这些监听器里面, 有一个非常重要的监听器ConfigFileApplicationListener
, 它将读取spring boot的的配置文件, 在接收到ApplicationEnvironmentPreparedEvent
之后, 会对所有的EnvironmentPostProcessor
执行postProcessEnvironment
,我们只看ConfigFileApplicationListener
的。
postProcessEnvironment
方法会直接执行addPropertySources
并创建一个Loader
然后,执行它的load
。
配置文件加载的顺序是如何的呢? 看看以下代码:
private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
return locations;
}
private Set<String> getSearchLocations(String propertyName) {
Set<String> locations = new LinkedHashSet<>();
if (this.environment.containsProperty(propertyName)) {
for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
return locations;
}
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
如果环境中有spring.config.location
这个属性, 就使用这个值, 如果没有,再去看环境中有没有spring.config.additional-location
这个属性, 将这个值作为待加载的一项配置, 再继续去读取默认配置的地址classpath:/,classpath:/config/,file:./,file:./config/
,
这里所有的这些属性都是可以传多个值,并以,
分隔开来, 例如在读取默认配置的地址时就是先将字符串以,
切开,再转换也LinkedHashSet
, 最后得到的列表为:
读配置文件的地址有了,现在就依次去读取所在文件夹下的配置文件, 读取的文件名则由getSearchNames
方法来确定, 在getSearchNames
中, 如果能从环境中读取到spring.config.name
, 则使用,否则使用默认的名称application
。
当然,spring.config.location
也是可以直接指定某一个文件,而不一定要是文件夹。
从/META-INF/spring.factories
中的配置可以看出,读取spring的配置文件会使用到两个PropertySourceLoader
:
首先是PropertiesPropertySourceLoader
去读, 它带的后缀是{ "properties", "xml" }
, 这时还没有任何一个配置文件被读取到,所以, 还不存在active profiles
这个概念, 第一个被读取的文件地址就是: file:./config/application.properties
, 我们现在使用的是一个默认的工程,这个目录是不存在的,所以,在代码追踪时会看到Skipped missing config 'file:./config/application.properties' (file:./config/application.properties)
, 这个文件会被跳过。 接着会去读取的文件是: file:./config/application.xml
, 同样也没有。
哇, 既然spring boot可以使用xml来写配置文件, 那之前我们的端口配置也可以写成下面这种形式:
创建一个application-pro.xml, 将下列内容写入文件中:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="server.port">9875</entry> </properties>
好像也像那么一回事儿, 关于最终会是哪一个被应用到工程中,后续再说。
file:./config/
这个目录下, PropertiesPropertySourceLoader
就读完了,然后, 采用YamlPropertySourceLoader
去读, 它带的后缀是{ "yml", "yaml" }
, 我上面一样,会去依次读取file:./config/application.yml
和file:./config/application.yaml
, 两个文件都不存在。
在路径规则是,它会使用PropertiesPropertySourceLoader
和YamlPropertySourceLoader
去读取这四个路径, 依次会读取到:
我样要是从来都没有配置过任何的applicaion文件,那所有的路径读取完成了之后,就会在系统是全部采用默认的配置数据了, 既然我们配置了,那就会在读取classpath:/application.yml
的时候获取到一个有效的配置文件。
在执行new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null), getProfiles(binder, ACTIVE_PROFILES_PROPERTY), getProfiles(binder, INCLUDE_PROFILES_PROPERTY))
的时候,是去刻意读取了spring.profiles.active
和spring.profiles.include
的, 所以这里,我们也得到一个Active Profiles
: pro
.
但还是会接着把所有的文件都读取完,不会因为拿到了一个Active Profiles
就中止读取。
接着pro
会被添加到环境中, 然后用这个Active Profiles
继续读配置文件, 读取的文件依次为:
这时,如果读到了文件的话,就会被存入到当前环境中, 这些读取出来的值还不能直接使用,毕竟里面可能存在占位符, 而且必须要声明一点的是, 无论你使用的是什么格式的文件, 最终在系统中都会统一成properties文件的格式。
接下来就是环境绑定和环境转换了。
SpringApplication
默认的banner是private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
, 也就是直接打印到控制台, 创建一个SpringApplicationBannerPrinter
, 调用print
, 但是打印的内容来自哪里呢?
Spring尝试获取文本和图片的Banner, 至少有一个存在才会打印出Banner,否则在没有显示的设置Banner的情况下,会直接使用默认的banner.
打印Banner的查找顺序如下:
spring.banner.image.location
对应的地址是否存在"gif", "jpg", "png"
的图片, 有的话,则加载进来。spring.banner.location
查找对应的文件地址,如果没有的话,则使用系统路径下的banner.txt
作为路径名, 如果存在,则加载进来。new SpringBootBanner()
的实例来打印banner, 即一个Spring
加上spring boot的版本号。
用于创建ApplicationContext的策略方法。默认情况下,在返回到适当的默认值之前,此方法将尊重任何显式设置的应用程序上下文或应用程序上下文类。
基于当前应用的类型, 它会加载一个org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
的类, 然后把它实例化了。
现在来看看这个被实例化的AnnotationConfigServletWebServerApplicationContext
是一个什么类.
以下是它的类继承关系:
实例化时使用的无参数构造方法。
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
* to be populated through {@link #register} calls and then manually
* {@linkplain #refresh refreshed}.
*/
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
这里初始化了两个类,一个是用于读取注解定义的bean, 一个是用于扫描类路径下的bean.
AnnotatedBeanDefinitionReader
在getOrCreateEnvironment
时,registry instanceof EnvironmentCapable
从继承关系图中可知是返回True的, 直接拿到当前环境。
接着创建conditionEvaluator
, 需要将如下内部变量都填充出来:
this.registry = registry;
this.beanFactory = deduceBeanFactory(registry);
this.environment = (environment != null ? environment : deduceEnvironment(registry));
this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
第一个就是AnnotationConfigServletWebServerApplicationContext
自身, beanFactory
也是自己返回出来的一个DefaultListableBeanFactory
实例, 来自于父类GenericApplicationContext
的构造方法创建的类:
/**
* Create a new GenericApplicationContext.
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
environment
本身不为空,直接返回。
resourceLoader
也是自己。
classLoader
返回的是ClassUtils.getDefaultClassLoader()
默认类加载器, 即AppClassLoader
。
创建完成conditionEvaluator
就开始执行AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
, 所执行的主要代码为:
/**
* Register all relevant annotation post processors in the given registry.
* @param registry the registry to operate on
* @param source the configuration source element (already extracted)
* that this registration was triggered from. May be {@code null}.
* @return a Set of BeanDefinitionHolders, containing all bean definitions
* that have actually been registered by this call
*/
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
try {
def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
AnnotationConfigUtils.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
}
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
return beanDefs;
}
拿到DefaultListableBeanFactory
, 检查是否包含某些bean, 不包含则创建:
检查的方式也就是从Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256)
中判断键是否存在this.beanDefinitionMap.containsKey(beanName)
, beanDefinitionMap
将会用来保存所有的bean.
如果不存在,则将该bean注册进去, 注册bean使用的方法是registerPostProcessor
.
private static BeanDefinitionHolder registerPostProcessor(
BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition);
return new BeanDefinitionHolder(definition, beanName);
}
注册bean的过程就是将键值对放入到beanDefinitionMap
中。
由于我们没有添加jpaPresent
支持,所有,最终会有五个Bean被注册成功。
ClassPathBeanDefinitionScanner
这个类没做太多事情, 首先将Component``ManagedBean``Named
包含到过滤器中, 然后设置环境,设置类加载器等操作。
到此, ApplicationContext
创建完成了。
环境也准备好了,上下文也创建了, prepareContext
这个环节主要是将环境与上下文结合在一起,然后做一些初始化的工作。
最主要的工作全部都在((AbstractApplicationContext) applicationContext).refresh()
中执行, AnnotationConfigServletWebServerApplicationContext
的初始化早已完成, 现在调用的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.
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();
}
}
}
在try catch之前的三个方法,主要是设置环境,PropertySources, 准备BeanFactory, 主要的工作都包含在了try catch中, 也是我们重要需要关注的位置。
invokeBeanFactoryPostProcessors
的调用层给比较深, 主要是对Bean进行注册。
在工程中我们并没有对需要扫包的路径进行配置,系统会自动根据main方法所有的包进行扫描, 在PostProcessorRegistrationDelegate
中invokeBeanFactoryPostProcessors
方法里调用了invokeBeanDefinitionRegistryPostProcessors
, 再深入到ConfigurationClassPostProcessor
类中的processConfigBeanDefinitions
, 这里定义了一个处理器:
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
上面有一行注释// Parse each @Configuration class
, 为什么会是处理所有的@Configuration
注解。 先看看这个方法执行完了之后,都得到了些什么结果再来看这个问题。
接着就开始执行parser.parse(candidates);
这里需要处理的BeanDefinitionHolder只有一个demoApplication
, 在ComponentScanAnnotationParser
的方法parse
中, 定义了ClassPathBeanDefinitionScanner
, 从名字可以看出, 这是通过类路径来对bean进行扫描的, 在没有设置basePackages
的情况下, 将通过ClassUtils.getPackageName(declaringClass)
直接截取出一个待扫描的包名, 当前类路径为com.refiny.demo.DemoApplication
, 截取出的待扫描包为com.refiny.demo
, 接着开始进行扫描scanner.doScan(StringUtils.toStringArray(basePackages));
.
Bean的扫描与注册步骤如下:
基于传入的待扫描包名可以得到一个完整的扫包规则:
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
得到的结果是: classpath*:com/refiny/demo/**/*.class
, 再由之前已经注册的一个beanServletContextResourcePatternResolver
来处理这个扫包规则, 它会首先将classpath*:
转化为硬盘路径, 然后就是一般的文件读取操作了:
最后存入Set<File> result
中。
这里就将所有的指定包下的字节码文件都读了出来,注意这里是所有呦, 并没有关注这个类是否是已经被注解标记过的。
接着对拿到的类进行处理, 依次检索每一个类, 判断它是否有注解Component
或者ManagedBean
, 判断出哪些是需要被管理的bean.
通过注解得到Bean的Scope, 设置到bean上, 从ScopeMetadata
类上,我们中以看到, 默认的scope是scopeName = BeanDefinition.SCOPE_SINGLETON
, ScopedProxyMode
类型是ScopedProxyMode.NO
.
/**
* Scope identifier for the standard singleton scope: "singleton".
* Custom scopes can be added via {@code registerScope}.
* @see #registerScope
*/
String SCOPE_SINGLETON = "singleton";
/**
* Scope identifier for the standard prototype scope: "prototype".
* Custom scopes can be added via {@code registerScope}.
* @see #registerScope
*/
String SCOPE_PROTOTYPE = "prototype";
Scope这里只写了两个, 但是可以通过registerScope
继续扩展。 比如往后启动服务器时注册的:
private void registerApplicationScope(ServletContext servletContext) {
ServletContextScope appScope = new ServletContextScope(servletContext);
getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
servletContext.setAttribute(ServletContextScope.class.getName(), appScope);
}
如果你使用Scope
注解进行过其它的定义, 那就是另的scope了。
默认的两个scope的作用分别是:
然后接着获取bean的名字, 尝试去获取bean上的注解, 判断这个注解是否属于org.springframework.stereotype.Component
, javax.annotation.ManagedBean
, javax.inject.Named
, 并且包含value
这个属性, 如果多个可用于定义Bean名字的注解上的value值不一致的话,就会抛出IllegalStateException
异常。
如果大家都没有写value的值, 那就采用默认的名称了, 拿到全限定名, 截取出类名, 变换一下返回出去, 他还举了一个例子:Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays as "URL".
, 意思就是如果第一位是大写,则转换为小写, 如果第一位和第二位都是大写,则没变化, 直接返回。
当前Bean如果不存在, 那就直接注册啦, 如果已经存在, 就判断一下两个bean是不是兼容的,如果不兼容就会报错ConflictingBeanDefinitionException
.
注册Bean是在DefaultListableBeanFactory
中完成的, 在确保名称和Bean的定义都不为空之后, 使用bean的名称去尝试获取一Bean, 如果不存在, 则把bean放入beanDefinitionMap
中, 这是一个ConcurrentHashMap
.
this.beanDefinitionMap.put(beanName, beanDefinition);
在synchronized
中对已定义Bean名称的更新采用的是:
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
而在synchronized
外, 则是很直接的:
this.beanDefinitionNames.add(beanName);
注册完成之后, 接着又对Bean上存在的其它注解进行解析,比如Import
, ImportResource
, Bean
等, 至此自己的Bean注册完了,然后再接着对系统的Bean进行注册完成。
Spring Boot是内置了Tomcat的, 当前版本的Tomcat是9.0.27。 spring boot尝试去读取有哪些可用的服务器, 如果一个都没有读出来,或者读出了多于一个的, 都会抛出异常, 在没有做出任何修改的情况下, 读取到的, 自然是默认的tomcatServletWebServerFactory
, 然后执行:
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
这是一个在spring中比较通用的流程, 去获取一个bean的实例出来, 来看一下这段创建Bean的代码:
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
这里使用了Lambda表达式, 执行结果就是返回一个创建出来的bean, 在createBean
中, 最主要的代码为:
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
好了, 接下来会做些很多事情, 我们重点关注一下服务器是如何被自定义配置的, 即之前在配置文件里的server.port
信息是如何被应用起来的。
BeanPostProcessors
在Bean创建后初始化前进行预处理。 for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
BeanPostProcessors
有很多, 例如:这里重点看一下WebServerFactoryCustomizerBeanPostProcessor
, 字面意思就是对服务器进行定义, 它是这么重写的:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
postProcessBeforeInitialization
方法如下:
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
他要做的是获取所有用于定义服务才需要的类, 包含如下一些Customizer:
customizer.customize(webServerFactory)
把服务器定义出来。这里又来重点看一下ServletWebServerFactoryCustomizer
这个服务器定义类的初始化。
这个类的构造函数是带了参数的:
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
所以,需要先把ServerProperties
这个bean创建出来。 ServerProperties
就不一样了, 它头上有一个注解ConfigurationProperties
, 所以, 创建bean的过程中ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
就不会再返回空了, 就需要执行一次this.binder.bind(bean);
动作, 这个动作的流程有点儿长。
主要的绑定逻辑是在JavaBeanBinder
中的:
private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier) {
boolean bound = false;
for (BeanProperty beanProperty : bean.getProperties().values()) {
bound |= bind(beanSupplier, propertyBinder, beanProperty);
}
return bound;
}
beanSupplier
是Binder
中的一个Lambda表达式:
(propertyName, propertyTarget) -> bind(name.append(propertyName), propertyTarget, handler, context, false, false);
现在就去找properties吧, ConfigurationPropertySource
来自于context.getSources(), 之前我们通过ConfigFileApplicationListener
读取出来的配置文件现在可以用上了, 另外这里也有很多系统本身就拥有的配置信息, 例如systemProperties
, systemEnvironment
等等。
还是把代码贴一下:
private ConfigurationProperty findProperty(ConfigurationPropertyName name, Context context) {
if (name.isEmpty()) {
return null;
}
for (ConfigurationPropertySource source : context.getSources()) {
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property != null) {
return property;
}
}
return null;
}
走到这里, 我们就应该回过头来考虑另外两个问题了:
读配置文件的时候, 是读到结果之后, 就立刻终止并返回了, 系统的属性是放在前面的, 因此即便是你写了类似os.name
在你的配置文件中, 也会获取到正确的值, 而不是你配置的值。 例如我们当前的端口配置信息server.port
, 读取文件的顺序是:
按这个顺序依次去获取值, 整理一下即, 先从文件根目录读取到工程目录, 每个目录下, config文件夹没有读取到再读根目录, 文件后缀依次为: properties > xml > yml > yaml;
配置文件读取出来之后,如果没有占位符, 会直接返回, 如果里面有占位符的话, 会在继续处理占位符:
private <T> Object bindProperty(Bindable<T> target, Context context, ConfigurationProperty property) {
context.setConfigurationProperty(property);
Object result = property.getValue();
result = this.placeholdersResolver.resolvePlaceholders(result);
result = context.getConverter().convert(result, target);
return result;
}
判断和处理占位符的逻辑为:
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
找到占位符里的内容, 又去source
里找找看是否有配置相应的值, 有的话, 就替换占位符, 没有的话, 那就原封不动的放在那儿。
serverProperties
拿到了, ServletWebServerFactoryCustomizer
的实例也就有了, 其它一干Customizer完成之后, 就会可以执行服务器定制了。
(customizer) -> customizer.customize(webServerFactory)
, 我们也仅仅来看看一下跟我们设置的server.port
相关的ServletWebServerFactoryCustomizer
, 它的customize
是这么重写的: @Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
}
这段代码言简义赅, 就是使用我们配置出来的serverProperties
对ConfigurableServletWebServerFactory
进行了自定义。至此, 服务器已经准备好了。
在最后, 还对一些不是lazy-init的实例进行了初始化, 然后就是启动服务器了。
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
清除缓存的资源信息, 继续发布事件。 其实这个ContextRefreshedEvent
还挺常用到的, 通常我都是在这个阶段开始对应用中我需要用到的信息开始初始化着走了。
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
`
启动服务器。