SpringBoot启动
前言
之前已经介绍了SpringBoot jar启动过程。
现在我们来看SpringBoot入口类main方法的执行。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
一个最简洁的SpringBoot入口启动类就是如上的结构,只需要调用SPringApplication这个类中的run方法就可以了。那我们就以这个方法为入口来对启动过程进行分析。
SpringBoot启动过程
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
我们发现会先调用SpringApplication的一个有参构造方法,参数为启动类Class对象。
public SpringApplication(Object... sources) {
initialize(sources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
// 把sources设置到SpringApplication的sources属性中,目前只是一个MyApplication类对象
}
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 判断是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必须可以被当前类加载器加载),并设置到webEnvironment属性中
this.webEnvironment = deduceWebEnvironment();
//从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化后设置到SpringApplication的initializers属性中。这个过程也就是找出所有的应用程序初始化器,这里用到了Springboot 的内部工具类 SpringFactoriesLoader
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//同上找出Key为ApplicationListener的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
获取到SpringApplication
对象实例后,调用run()
方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//打印Banner
Banner printedBanner = printBanner(environment);
//创建SpringBoot上下文环境
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, ex);
throw new IllegalStateException(ex);
}
}
- 先准备Environment,在
prepareEnvironment()
这个方法调用中,会在内部调用一次SpringApplication.run()
。 - 打印Banner图,这个我们在启动过程的日志中能够明显的看到。
- 创建SpringBoot程序上下文,在这个ApplicationContext中,会包含我们的SpringBoot启动类,在这里是MyApplication。因为我们已经在调用
SpringApplication.run(MyApplication.class, args);
显示的将启动类信息告诉了SpringApplication
- 刷新上下文,这个我们已经很熟悉了。也是我们今天要重点看的部分。这一步会将工程中的Bean都注入到Spring环境中。
- 后续的一些操作,在这里就不详细说了。
第4步的refreshContext(context);
方法的执行过程中最终会调用AbstractApplicationContext.refresh()
方法。
refresh过程
refresh()
方法在之前在分析SpringMVC中已经分析过了。我们来看下载SpringBoot环境中,这个方法的各个步骤具体的作用。
prepareBeanFactory(beanFactory)
在这个方法中,进行后续的一些BeanFactory操作,在SpringBoot中,这部分会将一系PostProcessor
处理器注入到Spring环境中。
invokeBeanFactoryPostProcessors(beanFactory)
接着会执行invokeBeanFactoryPostProcessors(beanFactory);
方法,会初始化当前Spring环境中的所有BeanFactoryPostProcessor实现类,并且调用相应的处理方法。ConfigurationClassPostProcessor是最先被处理的BeanFactoryPostProcessor实现类(该类直接实现的是BeanDefinitionRegistryPostProcessor(BeanFactoryPostProcessor的子接口),在上面一步被注入到Spring环境中)。(具体的逻辑见PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
)
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
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);
processConfigBeanDefinitions(registry);
}
该方法主要就是调用processConfigBeanDefinitions(registry)
方法来处理当前环境中带有@Configuration
注解的BeanDefinition
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略一大坨代码
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(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);
alreadyParsed.addAll(configClasses);
candidates.clear();
//检查是否在parse后有带来新的Bean
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<String>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
1.先收集到带有@Configuration
注解的Bean。
2.然后构造一个ConfigurationClassParser
对象,来对上述符合条件的Bean进行处理,处理的过程是一个循环。
3.在parse过程中,被解析出来的类会放到configurationClasses属性中。然后使用ConfigurationClassBeanDefinitionReader去解析这些类,在解析过程中会出现有新的bean被Reader
读取出来。在这个时候这些bean只是被加载到了Spring容器中。
3.检查是否在parse的过程中,是否有为当前环境带来新的Bean,如果有,找出新的Bean里面是否有带有@Configuration
(candidates
)的,如果有,那么继续进入下一次parse()
的过程。直到没有新的candidates
。
看具体的parse过程
ConfigurationClassParser#doProcessConfigurationClass
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
1.处理@PropertySources注解:进行一些配置信息的解析
2.处理@ComponentScan注解:使用ComponentScanAnnotationParser扫描basePackage下的需要解析的类(@SpringBootApplication注解也包括了@ComponentScan注解,只不过basePackages是空的,空的话会去获取当前@Configuration修饰的类所在的包),并注册到BeanFactory中(这个时候bean并没有进行实例化,而是进行了注册。具体的实例化在finishBeanFactoryInitialization方法中执行)。对于扫描出来的类,递归解析
3.处理@Import注解,具体下面会分析
4.处理@ImportResource注解:获取@ImportResource注解的locations属性,得到资源文件的地址信息。然后遍历这些资源文件并把它们添加到配置类的importedResources属性中
5.处理@Bean注解:获取被@Bean注解修饰的方法,然后添加到配置类的beanMethods属性中
6.处理DeferredImportSelector:处理第3步@Import注解产生的DeferredImportSelector,进行selectImports方法的调用找出需要import的类,然后再调用第3步相同的处理逻辑处理
这里@SpringBootApplication注解被@EnableAutoConfiguration修饰,@EnableAutoConfiguration注解被@Import(EnableAutoConfigurationImportSelector.class)修饰,所以在第3步会找出这个@Import修饰的类EnableAutoConfigurationImportSelector,这个类刚好实现了DeferredImportSelector接口,接着就会在第6步被执行。第6步selectImport得到的类就是自动化配置类。EnableAutoConfigurationImportSelector的selectImport方法会在spring.factories文件中找出key为EnableAutoConfiguration对应的值,有81个,这81个就是所谓的自动化配置类(XXXAutoConfiguration)。
最终对这些注解进行处理生成的数据,会在ConfigurationClassBeanDefinitionReader
的loadBeanDefinitions
方法中被处理。
重点来看下对@Import
注解的处理,这个在SpringBoot环境中应用的非常多。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader,
- 设计模式学习(三): 装饰者模式 (附C#实现)
- 浅谈非PE的攻击技巧
- hbase源码系列(十)HLog与日志恢复
- hbase源码系列(六)HMaster启动过程
- 如何写好一篇漏洞报告(国外篇)
- hbase源码系列(八)从Snapshot恢复表
- hbase源码系列(七)Snapshot的过程
- CVE-2017-3085:Adobe Flash泄漏Windows用户凭证
- hbase源码系列(九)StoreFile存储格式
- 如何确定恶意软件是否在自己的电脑中执行过?
- Carbondata源码系列(二)文件格式详解
- 挖洞经验 | 记一次针对Twitter(Periscope)API 的有趣挖洞经历
- 设计模式学习(二): 观察者模式 (C#)
- Carbondata源码系列(一)文件生成过程
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- s3cmd ls之迷惑
- 构建pyflink镜像
- apt-get update遇到NO_PUBKEY
- 遇到mpi worker exited on signal 9
- 容器共享GPU时查看容器使用的GPU编号
- oci runtime error: exec failed: container_linux.go:247: starting container process caused “exec: “/
- R|UpSet-集合可视化
- 美国队长的盾(一) 同心圆
- R|clusterProfiler-富集分析
- R|fastqcr QC数据处理
- R|timeROC-分析
- R|ML_code-线性回归(2)
- R|机器学习入门-多元线性回归(3)
- Bioinfo|bedtools-操作VCF文件
- 美国队长的盾(二)五角星