In the past, to develop a project, it took a lot of time to build the project and configure the file. Now Spring Boot is out of the box, and the technology stack needs to be imported into the pom. The efficiency tips brought by technology changes are huge. Sometimes I wonder, how did all this come about, how did Spring Boot abandon war deployment and cumbersome xml configuration.
Reading this article requires a certain amount of knowledge of the Spring framework. Finally, you can understand how Spring performs Bean initialization, and at least know the knowledge points such as BeanDefinition, in order to better read the article. The following code is based on Spring Boot 2.7.2 and Spring Cloud 2021.0.3.
First start the project and put it into the entry. Every Spring Boot project needs to call the main entry SpringApplication.run
to start
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 is an enumeration class that describes the current project web type
NONE: The current project is not a web project
SERVLET: traditional web project based on servlet api
REACTIVE: Spring webFlux reactive web framework
deduceFromClasspath : According to the project jar to determine which type of the current project belongs to the above, it is needed to create the Spring context object later
getSpringFactoriesInstances: From the given interface from the file META-INF/spring.factories
use the class name to load the full class name, and return all the implementation classes of the interface, the configuration file format is as follows
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
This JVM-like SPI mechanism, why Spring does not use SPI to introduce extended instances, I guess SPI does not satisfy the initialization of implementation classes with multiple construction parameters, and this mechanism is temporarily called: SpringFactoriesLoader loading .
BootstrapRegistryInitializer: The callback interface used to initialize BootstrapRegistry, which is called before BootstrapRegistry is used.
ApplicationContextInitializer: Initializes the callback interface of the ConfigurableApplicationContext before executing the Spring factory class to call AbstractApplicationContext.refresh (Spring factory core method bean initialization). Mainly to do a configuration file settings, property settings.
ConfigurableApplicationContext is an SPI interface used to initialize the ApplicationContext by configuration. Spring Boot, as the master of the Spring framework, the context object ApplicationContext is often different according to different environments. At this time, the interface of ApplicationContextInitializer is very necessary. Different components implement the interface to initialize the context object according to their own conditions.
ApplicationContextInitializer interface
DelegatingApplicationContextInitializer
: Through the environment variable context.initializer.classes
class name, load all ConfigurdiableApplicationContext subclasses, instantiate, and execute the ApplicationContextInitializer interface (interface parameter).
SharedMetadataReaderFactoryContextInitializer
: Register CachingMetadataReaderFactoryPostProcessor for registering SharedMetadataReaderFactoryBean with the container for caching Spring loaded resources
ContextIdApplicationContextInitializer
: Initialize ContextId
ConfigurationWarningsApplicationContextInitializer
: report @ComponentScan configuration error information input alarm log
RSocketPortInfoApplicationContextInitializer
: Create a listener event and assign server.ports to local.rsocket.server.port
ServerPortInfoApplicationContextInitializer
: Create web event listener: publish server namespace network port
ConditionEvaluationReportLoggingListener
: Create an event listener, spring initialization success or failure, print related information.
List of ApplicationListeners
EnvironmentPostProcessorApplicationListener
: Listen to the ApplicationEnvironmentPreparedEvent event, execute the EnvironmentPostProcessor configuration file preprocessor, and load the configuration file into the ConfigurableEnvironment
AnsiOutputApplicationListener
: Listen to the Spring just started event and load the ansi configuration from the configuration file.
LoggingApplicationListener
: Load log related configuration for initial setting.
BackgroundPreinitializer
: Initialize Formatter, Validation, HttpMessageConverter, jackson, UTF-8 settings through multi-threading.
DelegatingApplicationListener
: Load the listener class name from the configuration file key: context.listener.classes and instantiate and register it in the container
ParentContextCloserApplicationListener
: Listen to the parent container shutdown event, and pass the event to the child to pass it down.
ClearCachesApplicationListener
: clear class adder cache
FileEncodingApplicationListener
: Check whether the file.encoding of the current system environment is the same as the value set by spring.mandatory-file-encoding, if not, an IllegalStateException will be thrown, and the program will stop immediately
run method
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 framework startup process
- Get Java command line startup parameters, extract Spring configuration parameters from them, and convert them from corresponding variables
- Create a configuration file object ConfigurableEnvironment, there will be profile settings in the command line, so load the configuration file according to the profile, and execute the configuration file event
- The file has been loaded, check whether there is a configuration spring.beaninfo.ignore from the environment variable, if set, write it to ConfigurableEnvironment
- Start printing banner, usually see various banners are executed here
- Start creating ConfigurableApplicationContext, the Spring container factory context object
- Call ApplicationContextInitializer to set properties on the just created ConfigurableApplicationContext
- Start Spring container IOC, AOP
- Publish Spring boot completion event
- From all ApplicationRunner CommandLineRunner in the container, the entire Spring container startup process is completed in the run method, including Spring Cloud loading is also completed here. The following detailed analysis
prepareEnvironment()
, how the configuration file context is initialized
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() If the current environment is empty, the corresponding class will be selected according to the webApplicationType type for initialization. You may be wondering how the environment may have value, then play it and see, when I analyze Spring Cloud, you will return to the environment and do not need to create it.
ps: Environment uses PropertySource internally to distinguish different configuration files. Each source configuration has its own name, such as system variables systemProperties, environment variables systemEnvironment, and so on. Use a propertySourceList and a list to save all PropertySources, which will always be loaded first in front of the queue.
I wrote a listener above EnvironmentPostProcessorApplicationListener
, which handles the environmentPrepared event and uses SpringFactoriesLoader to load all EnvironmentPostProcessor preprocessors, one of which ConfigDataEnvironmentPostProcessor
is to read the configuration file, which also contains There are a lot of logical processing, which will not be expanded here. Interested students can analyze the code by themselves. Reading the file itself is also based on environment variables. Here are several Spring built-in configurations.
- spring.config.location sets the loading file path, if not, the class path is used./, config/
- spring.config.additional-location: Load external file paths, this can coexist with spring.config.location, with the highest priority
spring.config.name sets the file name prefix, the default application
The above variables are obtained from environment variables and system variables, but of course they will not be read from configuration files. The loading file is determined by setting the file path and file name. The rules for loading files are as follows${spring.config.location}/ ${spring.config.name}-${profile}.${extension}
- extension: There are 4 built-in file name suffixes, namely: properties, yml, xml, yaml
See how the ConfigurableApplicationContext is initialized
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组件如何加载实例化,这个依赖于前面两个。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。