以前开发一个我的项目,要花费不少工夫在搭建我的项目,配置文件上,到当初 Spring Boot 开箱即用,须要技术栈导入 pom 就能够了,技术变更带来效率提醒是微小的。有时候我会纳闷,这所有如何得来的,Spring Boot 怎么摈弃 war 部署,摈弃繁琐 xml 配置。
浏览本文章须要肯定的 Spring 框架常识储备,最初能理解 Spring 如何进行 Bean 初始化的,至多晓得 BeanDefinition 之类的知识点,能力更好阅读文章。上面代码基于 Spring Boot 2.7.2、Spring Cloud 2021.0.3。
先从我的项目启动放入入口,每一个 Spring Boot 我的项目都须要 main 入口都要调用 SpringApplication.run
开始
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] {primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//web 我的项目类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();}
webApplicationType 是一个枚举类,形容以后我的项目 web 类型
NONE:以后我的项目不是一个 web 我的项目
SERVLET: 基于 servlet api 的传统 web 我的项目
REACTIVE:Spring webFlux 响应式 web 框架
deduceFromClasspath : 依据我的项目 jar 判断以后我的项目属于下面哪个一个类型, 前面创立 Spring 上下文对象须要用到
getSpringFactoriesInstances:从给定接口从文件META-INF/spring.factories
应用类名去加载全类名,并且返回接口所有实现类,配置文件格式如下
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
这个相似 JVM 的 SPI 机制,对于 Spring 为什么没有应用 SPI 来 引入扩大实例,我猜 SPI 不满足多结构参数的实现类初始化,这里临时将这种机制称作:SpringFactoriesLoader 加载。
BootstrapRegistryInitializer:用于初始化 BootstrapRegistry 的回调接口,在 应用 BootstrapRegistry 之前调用它。
ApplicationContextInitializer:在执行 Spring 工厂类调用 AbstractApplicationContext.refresh(Spring 工厂外围办法 bean 初始化)之前初始化 ConfigurableApplicationContext 的回调接口。次要是做一个配置文件设置、属性设置。
ConfigurableApplicationContext 是一个 SPI 接口用于通过 配置形式初始化 ApplicationContext。Spring Boot 作为 Spring 框架的集大成者上下文对象 ApplicationContext 往往依据不同环境有所区别的,这时很须要 ApplicationContextInitializer 这种接口,由不同组件依据本身状况去实现接口初始化上下文对象。
ApplicationContextInitializer 接口
DelegatingApplicationContextInitializer
:通过环境变量 context.initializer.classes
类名,加载所有 ConfigurdiableApplicationContext 子类,实例化,排序执行 ApplicationContextInitializer 接口(接口参数)。SharedMetadataReaderFactoryContextInitializer
: 注册 CachingMetadataReaderFactoryPostProcessor 用于向容器注册 SharedMetadataReaderFactoryBean,用于缓存 Spring 加载资源ContextIdApplicationContextInitializer
: 初始化 ContextIdConfigurationWarningsApplicationContextInitializer
:报告 @ComponentScan 配置错误信息输出告警日志RSocketPortInfoApplicationContextInitializer
: 创立一个监听事件,将 server.ports 赋值到 local.rsocket.server.portServerPortInfoApplicationContextInitializer
:创立 web 事件监听:公布 server namespace 网络端口ConditionEvaluationReportLoggingListener
: 创立一个事件监听,spring 初始化胜利或失败,打印相干信息。
ApplicationListener 列表
EnvironmentPostProcessorApplicationListener
: 监听 ApplicationEnvironmentPreparedEvent 事件,执行 EnvironmentPostProcessor 配置文件前置处理器,加载配置文件到 ConfigurableEnvironmentAnsiOutputApplicationListener
: 监听 Spring 刚启动事件,从配置文件加载 ansi 配置。LoggingApplicationListener
: 加载日志相干配置进行初始化设置。BackgroundPreinitializer
: 通过多线程形式初始化 Formatter、Validation、HttpMessageConverter、jackson、UTF- 8 设置。DelegatingApplicationListener
: 从配置文件 key:context.listener.classes 加载监听器类名并实例化注册到容器中ParentContextCloserApplicationListener
: 监听父级容器敞开事件,并且将事件传递到子级逐级传递下取。ClearCachesApplicationListener
: 革除类加器缓存FileEncodingApplicationListener
: 检测以后零碎环境的 file.encoding 和 spring.mandatory-file-encoding 设置的值是否一样,如果不一样的话,就会抛出一个 IllegalStateException 异样,程序启动立马进行
run 办法
public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();
// 调用 BootstrapRegistryInitializer 接口对上下文进行初始化
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 设置 java.awt.headless 缺失显示设施须要 CPU 染指显示
configureHeadlessProperty();
// 获取事件公布器实例,这里会将下面监听器实例装进公布器,监听器相似事件消费者
SpringApplicationRunListeners listeners = getRunListeners(args);
// 公布 starting 事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 获取所有启动参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 创立配置文件对象
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 从配置文件中漠视 bean
configureIgnoreBeanInfo(environment);
//Banner 配置 打印
Banner printedBanner = printBanner(environment);
// 应用 ApplicationContextFactory 初始化 ApplicationContentx Spring 工厂
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 配置文件对象配置
// 开始对 applicationContext context 进行初始化
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context); // 调用 refresh
// 空办法
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
// 调用所有 ApplicationRunner CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
run()
Spring Boot 框架启动流程
- 获取 Java 命令行启动参数,从中提取 Spring 配置参数,转换从对应变量
- 创立配置文件对象 ConfigurableEnvironment,命令行中会有 profile 设置,所以要依据 profile 加载配置文件,在执行配置文件事件
- 曾经加载好文件了,从环境变量中检测是否存在配置 spring.beaninfo.ignore,如果设置,写入到 ConfigurableEnvironment 中
- 开始打印 banner, 平时看到各种 banner 就是在这里执行
- 开始创立 ConfigurableApplicationContext,Spring 容器工厂上下文对象
- 对刚刚创立 ConfigurableApplicationContext 调用 ApplicationContextInitializer 进行属性设置
- 启动 Spring 容器 IOC、AOP
- 公布 Spring 启动实现事件
- 从容器中所有 ApplicationRunner CommandLineRunner 在调用办法
在 run 办法外面就实现实现整个 Spring 容器启动流程了,包含 Spring Cloud 加载也是这里实现的。上面详细分析prepareEnvironment()
,配置文件上下文如何初始化的
prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 依据 webApplicationType 创立
// SERVLET => ApplicationServletEnvironment
//REACTIVE=> ApplicationReactiveWebEnvironment
// NONE => ApplicationEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 命令行可能会有 profile,能够抉择那个 profile,也会将命令行参数生成一个 PropertySources
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 增加 configurationProperties PropertySource 到 propertySourceList 队列最后面
ConfigurationPropertySources.attach(environment);
// 执行所有 SpringApplicationRunListener
listeners.environmentPrepared(bootstrapContext, environment);
// 将 defaultProperties sources 移致队尾
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 从配置文件对应 spring.main.* 属性注入
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
// 将类型转换器设置到 environment
environment = convertEnvironment(environment);
}
// 因为 EnvironmentPostProcessor 可能加载到配置文件里,这时须要 configurationProperties 放入第一
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment() 如果以后 environment 如何为空,则会依据依据 webApplicationType 类型抉择对应类进行初始化。大家可能好奇 environment 怎么可能有值呢,接着玩下看,当我剖析 Spring Cloud 时你就会返回 environment 不须要创立了。
ps: Environment 外部应用 PropertySource 辨别不同配置文件,每一个源配置都有本人的名字,比方零碎变量 systemProperties、环境变量 systemEnvironment 等等。应用一个 propertySourceList 一个 list 将所有 PropertySource 保存起来,在队列后面永远最优先加载。
在下面写过一个监听器 EnvironmentPostProcessorApplicationListener
,它解决 environmentPrepared 事件,应用 SpringFactoriesLoader 加载所有 EnvironmentPostProcessor 前置处理器,其中之一ConfigDataEnvironmentPostProcessor
就是去做读取配置文件,外面还有很多逻辑解决,这里就不开展了,有趣味的同学自行去剖析代码。读取文件自身也是依据环境变量来的,这里有几个 Spring 内置配置
- spring.config.location 设定加载文件门路,没有则是应用类门路./、config/
- spring.config.additional-location: 加载内部文件门路,这个能够 spring.config.location 共存,优先级最大
-
spring.config.name 设定文件名前置,默认 application
下面这些变量都是从环境变量、零碎变量中获取的,当然不会从配置文件读取到。通过设定文件门路、文件名这样形式确定加载文件, 加载文件规定如下${spring.config.location}/ ${spring.config.name}-${profile}.${extension}
- extension:文件名后缀 内置反对 4 种,别离是:properties、yml、xml、yaml
看下 ConfigurableApplicationContext 如何被初始化的
prepareContext
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 初始化 ConfigurableEnvironment
context.setEnvironment(environment);
// 初始化 resourceLoader ConversionService
postProcessApplicationContext(context);
// 执行下面从 SpringFactoriesLoader 加载 ApplicationContextInitializer 对 ConfigurableApplicationContext 属性设置
applyInitializers(context);
// 调用 SpringApplicationRunListener.contextPrepared 事件
listeners.contextPrepared(context);
// 执行 BootstrapContextClosedEvent 事件
bootstrapContext.close(context);
if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 上面增加特定单例对象,为 Spring 初始化 bean IOC 解决必要的 bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 这里曾经从配置文件加载 设置到本身属性上了,这时设置给上下文对象
// allowCircularReferences 容许同名 bean 笼罩 lazyInitialization 对所有 bean 应用懒加载
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 这个前置处理器次要作用就是将配置 defaultProperties 移到队尾
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources 这里有启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 初始化 BeanDefinitionLoader 并将启动类注册成 BeanDefinition
load(context, sources.toArray(new Object[0]));
// 所有监听器执行 contextLoaded 事件
listeners.contextLoaded(context);
}
````
在这里实现了 Spring 容器初始化,下一步就是启动了。### bean 初始化
其实我始终很好奇 @Configuration 这个注入如何实现配置类,还有还么多 Class 要被 Spring 进行初始化,如何变成 BeanDefinition 最初变成 bean。我确定从 `AbstractApplicationContext.refresh()`debug, 终于被我发现 Spring 魔法,在 `invokeBeanFactoryPostProcessors()`
在执行 invokeBeanFactoryPostProcessors 办法中回去获取 `BeanDefinitionRegistryPostProcessor` 类型内置对象,并且执行所有实现类。- `BeanDefinitionRegistryPostProcessor`: 你能够了解成 BeanDefinition 注册前置处理器,次要就是生成 BeanDefinition,再还给容器。在 Spring 还没有初始化 bean 时,这个接口运行实现类去初始化 BeanDefinition 再交还给 Spring 工厂对象, 简白点就是这个对象会创立 BeanDefinition,交给 Spring,后续进行初始化 bean。上面要解说其中一个实现类 `ConfigurationClassPostProcessor`
#### postProcessBeanFactory 创立 postProcessBeanFactory
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) { // 这个办法只能执行一次,通过记录上下文 id 标记执行
throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against" + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against" + registry);
}
this.registriesPostProcessed.add(registryId);
// 解析 Class 生成 BeanDefinition
processConfigBeanDefinitions(registry);
}
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
//candidateNames 为后期应用 BeanDefinitionRegistry 增加进去单例对象,除了领有 Spring 工厂对象外还有
// SpringBoot main 启动类 这里能起到作用就是 Spring Boot main 函数
for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class:" + beanDef);
}
}
// 查看 beanDef 是不是配置类,带有 @Configuration 都算
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 这里不存在没有配置类,只有配置 @SpringBootApplication Class 就是一个配置类
if (configCandidates.isEmpty()) {return;}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
// ConfigurationClassParser 看名字就晓得,这是一个解析 @Configuration 解析类
// 将解析 Class 工作专门委派给 parse 去做了,解析后的后果会变成 ConfigurationClass
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do { // 这里是一个循环
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
// 曾经将所有配置类全副解析进去 变成 ConfigurationClass
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed); // 删除曾经解析过
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses); // 将所有 ConfigurationClass 转化 BeanDefinition,并注册到容器中
alreadyParsed.addAll(configClasses); // 增加曾经注册过的,下面删除对应
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
candidates.clear();
// 当 ConfigurationClassParser 解析出 ConfigurationClass 就会大于 candidateNames
if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);
// bd 就是一个配置类
// bd 曾经注册到容器中,然而不是在 ConfigurationClassParser 解析进去的后果,则阐明 bd 并没有通过解析生成
// 可能为第三方 BeanDefinitionRegistryPostProcessor 生成 BeanDefinition,退出到 candidates 再次进入循环中
// 被 ConfigurationClassParser 解析,能够生成更多 BeanDefinition
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty()); // 当所有 BeanDefinition 都曾经被解析完了,循环就能够退出了
// 上面省略
}
看完下面的代码,ConfigurationClassPostProcessor 就是 Spring 将带有 @Configuration 标记 Class 通过一系列解决生成 BeanDefinition 的机制。在 @SpringBootApplication 中有个一个 @EnableAutoConfiguration 带有 @Import(AutoConfigurationImportSelector.class),这个会被 ConfigurationClassPostProcessor 解析加载。其中 AutoConfigurationImportSelector 应用 SpringFactoriesLoader 加载,会将所有 @EnableAutoConfiguration 的配置类全副都加载 ClassName,能够让 Spring Boot 加载 ScanPackage 根底包门路之外的配置类,再通过 @ConditionalOnBean、@ConditionalOnProperty 这类注解,依据 Class、配置判断是否进行解析。也就是说 Spring Boot 一开始就曾经获取到所有配置类,只有当符合条件时才会进入解析、加载、实例化。### Spring Cloud
下面说了 Spring Boot 自动化配置接下来就是 Spring Cloud 方面,看了下面源码,发现没有代码无关 Spring Cloud,当初还不晓得配置核心的配置如何作用到曾经开始运行 Spring 容器中。在开始剖析代码之前,先简略看一个例子
![image.png](https://upload-images.jianshu.io/upload_images/9213940-ebb638f2b745c88e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
能够看到 applicatioinContext 有一个父级上下文,而这个就是 Spring Cloud 上下文对象。看到这个是不是很惊奇,这个父级上下文在哪里初始化的呢,从代码角度去看了。下面剖析过 ApplicationListener 监听器中,在 Spring Cloud lib jar 中有一个实现类 BootstrapApplicationListener,通过它来启动 Spring Cloud。
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment environment = event.getEnvironment();
// 两个条件 environment 配置 spring.cloud.bootstrap.enabled 或者某个类是否存在,其实就是 spring-cloud-starter-bootstrap jar class
// 配置 spring.config.use-legacy-processing 这个配置是用来兼容旧版本配置文件加载
// 我这里环境引入 spring-cloud-starter-bootstrap 第一个条件返回 true,第二条件不必判断
if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {return;}
// don't listen to events in a bootstrap context
// 判断 environment 是否曾经存在 bootstrap 文件,曾经加载过不须要往下执行了
// 当父级初始化也会执行监听器事件,到时来到这里时,父级监听器不会往下执行了
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {return;}
ConfigurableApplicationContext context = null;
// 默认配置文件名,没有在环境变量配置默认就是 bootstrap
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {if (initializer instanceof ParentContextApplicationContextInitializer) { // 在曾经存在 ParentContextApplicationContextInitializer 中返回父级容器
context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
}
}
if (context == null) { // 当下面 ParentContextApplicationContextInitializer 没有执行就会走上面初始化父级容器办法
// 这里会返回父级容器,也就是 Spring Cloud 上下文对象
context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
// 从父级容器中获取 ApplicationContextInitializer 交给 SpringApplication
// 父级生成 ApplicationContextInitializer 用于增强子类
apply(context, event.getSpringApplication(), environment);
}
这个监听器次要依据配置文件信息来启动 Spring Cloud 组件,如果没有相应的配置依据我的项目环境来,看下 Spring Cloud 上下文如何被初始化进去的。
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
final SpringApplication application, String configName) {ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() { };
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
// 应用代码生成一个 Spring Cloud 加载文件的配置信息,规定相似下面加载 applicaton 配置
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {bootstrapMap.put("spring.config.location", configLocation);
}
if (StringUtils.hasText(configAdditionalLocation)) {bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
}
// 将加载文件的配置信息放入配置文件上下文 environment
bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {if (source instanceof StubPropertySource) {continue;}
bootstrapProperties.addLast(source);
}
// TODO: is it possible or sensible to share a ResourceLoader?
// SpringApplicationBuilder 为 SpringApplication 包装类,从新生成 SpringApplication 来创立 ApplicationContext 上下文
SpringApplicationBuilder builder = new SpringApplicationBuilder().profiles(environment.getActiveProfiles())
.bannerMode(Mode.OFF).environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
}
/ BootstrapImportSelectorConfiguration
builder.sources(BootstrapImportSelectorConfiguration.class);
// 这里将调用 SpringApplication.run 下面曾经剖析,final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
// 这里增加 AncestorInitializer 是一个 ApplicationContextInitializer 实现类,目标就是让子 applicationContext 和父级关联起来
addAncestorInitializer(application, context);
// 以后 environment 为子集配置对象,这里要删除掉父级加载文件信息
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
// 将 springCloudDefaultProperties 配置文件信息 copy 到 environment 中
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
当初所有代码都看完了,咱们来理一理整一个流程就会清晰明了。在 BootstrapApplicationListener 中会依据配置文件或者是我的项目环境 jar 来是否启动加载 bootstrap 配置文件。先从生成加载 Spring Cloud 配置信息,应用 SpringApplicationBuilder 来构建 SpringApplication 对象,然
后执行 SpringApplication.run 办法,这个代码咱们曾经剖析过了,初始化 Spring 容器上下文对象,而后进入
外围 refresh 办法执行 IOC。SpringApplicationBuilder 结构 SpringApplication 中没有像咱们写启动类 main
办法,会设置启动类 Class。所以被 ConfigurationClassPostProcessor 解析 BeanDefinition, 并没有
@SpringApplication 这个注解,所以这个 Spring Cloud 工厂没有获取到 basepackae、@EnableAutoConfiguration 这些货色。依据下面代码晓得 Spring Cloud 将
BootstrapImportSelectorConfiguration 作为 BeanDefinition 交给 ConfigurationClassPostProcessor,
这样父级容器只有加载 BootstrapConfiguration 标记类,父级 bean 和子级 bean 互相隔离。这样父级容器就能够
去启动与 Spring Cloud 无关的 bean。当 Spring Cloud 容器曾经实现 bean 初始化后,再来执行 SpringApplicaton.run
启动 Spring 容器创立。这样在子级启动之前曾经将配置核心的配置对应的对象曾经创立进去了。再通过
ApplicationContextInitializer 接口将配置对象加载 ConfigurableEnvironment 中。这里应用较短的篇幅来剖析 Spring Boot 这个框架如何工作, 站在本人的思维上,应用 3 个知识点来展现 Spring
Boot 技术细节实现。第一个从 SpringApplication.run 理解 Spring 两大工厂对象
ConfigurableApplicationContext、ConfigurableEnvironment 如何初始化解决进去的,配置文件如何被
加载的,加载规定,知识点 SpringFactoriesLoader 机制,如果要做 Spring Boot 组件必须要这个。理解了
Spring Boot ApplicationContextInitializer、ApplicationListener 这些接口,还有
SpringApplicationRunListener 为整个 Spring Boot 事件监听器,对应整个框架的不同阶段解决。第二
简略剖析了 Spring 容器启动时如何生成 BeanDefinition 的机制实现类:BeanDefinitionRegistryPostProcessor,理解了 Spring Boot 组件如何被加载、实例化,这个依赖启动类
的注解。最初 Spring Cloud 组件如何加载实例化,这个依赖于后面两个。