曾经开发一个项目,要花费不少时刻在建立项目,装备文件上,到现在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
: 初始化ContextId
ConfigurationWarningsApplicationContextInitializer
:报告@ComponentScan装备错误信息输入告警日志
RSocketPortInfoApplicationContextInitializer
: 创立一个监听事情,将server.ports赋值到 local.rsocket.server.port
ServerPortInfoApplicationContextInitializer
: 创立web事情监听: 发布server namespace网络端口
ConditionEvaluationReportLoggingListener
: 创立一个事情监听,spring初始化成功或失败,打印相关信息。
ApplicationListener列表
EnvironmentPostProcessorApplicationListener
: 监听ApplicationEnvironmentPreparedEvent事情,履行EnvironmentPostProcessor 装备文件前置处理器,加载装备文件到ConfigurableEnvironment
AnsiOutputApplicationListener
: 监听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.location}/ {spring.config.name}-profile.{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 容器中。在开端剖析代码之前,先简略看一个例子
能够看到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组件怎么加载实例化,这个依赖于前面两个。