每个spring boot项目都有一个启动类,如下所示:
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
从上面可以看出来,项目的启动主要与@SpringBootApplication注解和SpingAppliction.run方法有关,项目启动的主要流程在run方法中,先说一下@SpringBootApplication注解,之后再对启动流程做个简单介绍。
一、@SpringBootApplication
先点进去看一下它的代码,如下
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
可以看到,@SpringBootApplication注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解组成,如果我们不使用@SpringBootApplication,直接将这三个注解放在启动类之上,项目也可以直接运行。
1、@SpringBootConfiguration注解大家可以点进去看一下,它里面只有一个@Configuration注解,并没有什么特别,和@Configuration注解的功能是一样的,仅仅是表明这是一个配置类,服务启动时可以将其加载进上下文。其实@Configuration也仅是一个特殊的@Component,可以看下这个,我就不写了@Component和@Configuration作为配置类的差别_component和configuration的区别-CSDN博客
2、@EnableAutoConfiguration表示开启自动加载配置,它加载的是哪里的配置呢?可以点进去看一下它的代码,它的实现主要就是在@Import(EnableAutoConfigurationImportSelector.class)上。
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
//EnableAutoConfigurationImportSelector类
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
@Import注解用于@Configuration配置类之上,可以导入bean、registrar、selector的实现类如@Import(xxx.class)、@Import(xxxxConfiguration.class)、@Import(xxxxselector.class),在@EnableAutoConfiguration中导入的是一个selector,就是用它来进行自动加载特定的配置。EnableAutoConfigurationImportSelector.class点进去可以看到它继承了AutoConfigurationImportSelector.class类,父类中有一个selectImports方法,它返回的就是由springboot自动加载的配置的完整类名,代码如下
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);//调用的getCandidateConfigurations获取需加载配置的完整类名
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
在selectImports方法中调用了getCandidateConfigurations方法,可以看到其中实际是利用了SpringFactoriesLoader中的loadFactoryNames方法,继续跟进去,其主要有loadFactoryNames和loadFactories两个方法,前者是获取需要加载的完整类名列表,后者是将其实例化并返回。那么是从何处获取这些类名呢?可以看到loadFactoryNames有两个参数一个是factoryClass,即需要加载的类在sping.factories文件中所对应的key,另一个是classloader,即它的类加载器。这里传入的factoryclass正是EnableAutoConfiguration.class。loadFactoryNames方法会扫描整个项目的所有META-INF/Sping.factories文件,大家可以打开看一下,(ps:如果自己项目中没有用到,可以搜一搜看下sping-boot-autoconfig包下的)可以发现它是一个key-value的形式,key可以是接口、注解、抽象类的全名称,value是key实现类,当有多个时用“,”分割。我们可以看到其中一个key正是org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以上面selectImports方法所获取的list正是它所对应的value。还记得我们前面说过的@Import注解吗,它可以获取所有EnableAutoConfiguration对应的实现类的完整类名,在后面run方法的refreshContext过程的invokeBeanFactoryPostProcessors中正是通过它获取完整类名,反射拿到对应的beanDefinition放入beanFactory的beanDefinitionMap之中。spring.factories文件实例如下,截了一部分:
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
3、@ComponentScan 开启自动扫描,默认情况下是扫描本目录及其子目录下的类,将符合条件的类的BeanDefinition放入上下文的beanFactory中
二、Spring-Boot启动流程
默认的启动方法只有SpringApplication.run(xxxConfiguration.class,args);这一句,我们可以点进去run方法看一下。它会先创建一个SpringApplication对象,然后再调用该对象的run方法,那么下面我会分两步,简单的介绍一下启动的流程。
1.创建SpringApplication对象
在创建SpringApplication对象时,它主要做了以下工作
(1)判断应用是否是web项目 ,通过ClassUtils.isPresent(String className, ClassLoader classLoader)方法,通过类加载器能否加载指定的类,去判断的:
servlet:(javax.servlet.Servlet, org.springframework.web.context.ConfigurableWebApplicationContext),基于servlet的web服务,并且会启动内置web服务器 tomcat,同步阻塞
reactive:org.springframework.web.reactive.DispatcherHandler,基于reactive的web服务,启动netty,异步非阻塞(spring-cloud-gateway就是)
servlet和reactive的区别:Spring Security 介绍中的 servlet 和 reactive - 知乎
(2)利用SpringFactoriesLoader中的loaderFactoryName方法获取Sping.factories文件中ApplicationContextInitializer.class application上下文初始化器及ApplicationListener.class application监听器对应的类名,然后通过BeanUtils.instantiateClass获取其实例对象(应该是通过反射),并将其赋值给SpringApplication对象中对应属性。在这一步是启动过程中第一次使用SpringFactoriesLoader的loadFacoryNames方法,此时会将所有key-value数据以LinkedMuliValueMap<String,List>的形式进行存储,然后以key为classloader存储在缓存中,之后就可以根据class取它对应的List。
(3)通过堆栈信息获取启动main方法所在类,并通过反射获取其对应的class ,并赋值到mainApplicationClass
this.webEnvironment = deduceWebEnvironment(); //判断项目的web类型
//利用SpringFactoryLoader.loadFactoryName和反射获取对应的ApplicationContextInitializer和ApplicationListener实例list并赋值给SpringApplication对象的属性
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass(); //获取main方法所在类
至此SpringApplication对象创建完毕接下来就是调用它的run方法
2、run方法执行流程
(1)getRunListeners获取SpringApplicationRunListener所对应的实例,并发布start事件,大家还记得再new SpringApplication对象的时候我们已经使用过SpringFactoriesLoader的loadFactoryName方法了,那么所有META-INFO/spring.factory下的key value的对应关系均已记入内存,那么在getRunListeners再次使用的时候,是根据key SpringApplicationRunListener获取其对应的类名,然后再实例化为具体对象的。
不知道大家有没有疑惑,在new SpringApplication时,已经获取了ApplicationListener的对象list并赋值给了SpringApplication的一个属性,那么它与run方法中的SpringApplicationRunListener对象的list有什么区别呢?大家可以点进去这个listeners发布的start事件,可以发现SpringApplicationRunListener是一个接口,它的实现类EventPublishingRunListener在构造方法中获取了SpringApplication对象及其存储applicationListener的对象list的属性将其添加进了SimpleApplicationEventMulticaster initialMulticaster应用事件多播对象,其实发布start事件,调用的是initialMulticaster的mulitCastEvent多播事件方法,在该方法会根据事件类型从ApplicationListener接口实现类对象的list获取监听该事件的listener,然后调用他们的onApplicationEvenet方法进行事件发送,这就是一个典型的观察者模式。
(2)prepareEnvironment在该方法中会根据前面获取的应用web类型的不同使用不同的ConfigurableEnvironment接口实现类创建运行环境对象,并获取配置参数进行初始化。最后会发布environmentPrepared运行环境已准备事件
(3)createApplicationContext,在该方法中会根据应用web类型(就是在创建SpringApplication对象时获取的web type)的不同以不同的实现类创建ConfigurableApplicationContext对象。由于大部分项目都是web类型的,(spring-boot 2.x.x) applicationContext一般都是AnnotationConfigServletWebServerApplicationContext,此外在也会生成beanFactory实例,beanFactory默认为DefaultListableBeanFactory,以及AnnotatedBeanDefinitionReader reader和ClassPathBeanDefinitionScanner scanner对象,在这一步之后一些spring默认的processor(处理器)的beanDefinition加载进beanFactory,如对@Autowired注解和@Configuration注解相关的处理器
(4)prepareContext,在prepareEnviement中已经获取了的应用运行环境,在这里会将环境信息应用于context上下文;发布contextPrepare事件。在创建SpringApplication对象时,也获取了一些用于上下文初始化ApplicationContextInitializer接口的实现类,在这里会调用它们的initialize方法将其用于context初始化。之后会调用getAllSource()获取创建SpingApplication对象时传入的source参数,实际就是使用@SpingBootApplication注解修饰的启动类,调用load方法加载source,在该方法中会获取BeanDefinitionLoader这样一个bean定义加载器对象->loader,使用loader.load(),最后通过一个AnnotatedBeanDefinitionReader把启动类的BeanDefinition加载到应用上下文context的beanFactory的beanDefinitionMap中。BeanDefinitionLoader对象中主要是有3个对象,AnnotatedBeanDefinitionReader annotatedReader、 ClassPathBeanDefinitionScanner scanner、XmlBeanDefinitionReader xmlReader,annotatedReader用于加载class类型的beanDefinition,scanner用于加载包路径上的beanDefinition,xmlReader用于加载source类型即配置类的beanDefinition。最后会发布contextLoaded事件。在load这一步仅会将启动类configuration的beanDefinition加载进beanFactory之中,大家可以看到它是从sources中中加载的,点进去getAllSources看一下,可以发现它里面是获取SpringApplication对象的primarySources属性,这个属性就是new SpringApplication方法传进去的主configuration
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//省略了
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
}
(5)refreshContext(这里讲得不好)
prepareRefresh()
beanFactory = obtainFreshBeanFactory()
parepareBeanFactory(beanFactory)
postProcessBeanFactory(beanFactory)
invokeBeanFactoryPostProcessors(beanFactory)
registerBeanPostProcessors(beanFactry)
initApplicationEventMulticaster
onRefresh
registerListeners
finishBeanFactoryInitialization(beanFactory)
finishRefresh()
在这个里面做的事比较多,
(a)在prepareRefresh中会设置刷新时间、标志位以及一些属性,简单来说就是对刷新做一些准备工作;
(b)调用obtainFreshBeanFactory获取上下文中的beanFactory,后续beanDefinition和object都是通过它存储的;
(c)prepareBeanFactory、postProcessBeanFactory两者都是对beanFactory中的一些属性进行设置
(d)invokeBeanFactoryPostProcessors,它是刷新过程中很重要的一步,在这一步会执行它会执行BeanFactoryPostProcessor的实现类,在这里需要重点指出的是有一个ConfigurationClassPostProcessor它实现了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor,因此ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPosrProcessor接口中的postProcessBeanDefinitionRegistry()方法,也实现了BeanFactoryPostProcessor中的postProcessBeanFactory方法,并且postProcessBeanDefinitionRegistry方法在postProcessBeanFactory之前执行,那么它的作用是做什么的呢?刚才在prepareContext阶段仅仅是把启动类的beanDefinition放入beanFactory的beanDefinitionMap中,那么在postProcessorBeanDefinitionRegistry方法中,会获取当前beanDefinitionMap中的@Configuration标识的beanDefinition,然后看它有没有@ComponentScan和@Import、@Bean注解,因为一开始configuration类型的beanDefinition只有我们的主类,且其中的@SpringBootApplication包含了ComponentScan注解和EnableAutoConfiguration注解,因此它会自动扫描包将其中使用@Component注解标记的类的beanDefinition放入beanDefinitionMap里去,也会装载配置类中@bean标注beanDefinition,同时@EnableAutoConfiguration注解中包含了@import注解(此时就和我们上面讲的串上了,会使用SpringFactoriesLoader.loadFactoryNames方法获取需要自动加载的完整类名通过反射将beanDifinition放入map中),并且也会加载这些自动配置类中使用@bean标识的方法,直到所有的bean定义被加载入beanFactory中。ConfigurationClassPostProcessor的postProcessBeanFactory会在postProcessBeanDefinitionRegistry方法之后执行,它会对我们的configuration bean进行enhancer代理增强,如果在某configuration中有两个使用@bean注解修饰方法,getA()和getB()返回A、B对象,且在getB()中使用了getA()那么,在getB中getA方法获取的a对象和外部的getA方法返回的a对象是同一个。它会保证每一个@bean注解生成的bean都是单例的,当在出现上面说的在一个getB中调用了getA,会首先去容器搜索@Bean修饰的这个对象是否在容器中存在,如果存在就直接返回容器中的对象,不存在就会调用@Bean对应的方法逻辑构建一个bean对象放入容器中并返回这个对象。以上这种机制是通过cglib代理中的拦截器实现的.
(e)registerBeanPostProcessors,注册beanPostProcessor接口实现类,用于在创建bean的过程中调用
(f)initApplicationEventMulticaster 初始化应用事件多播器,用于发布事件
(g)onRefresh,springBoot内置了webServer容器如tomcat,在这一步就会创建webserver对象并启动
(h)registerListeners向事件多播器注册监听器
(i)finishBeanFactoryInitialization对所有的非懒加载单例的beanDefinition进行初始化成为真正的bean,并存入beanFactory的一级缓存singletonObjects之中
(j)finishRefresh做一些刷新的收尾工作
(6)afterRefresh
这里流程是比较简单的,如果我们自己实现了ApplicationRun或CommanLineRun接口的话,在这里会调用实现类的run方法。我现在项目里面flyway模块继承ApplicationRun使用到了afterRefresh,它会在该步骤执行
@Deprecated
@Component
public class FlywayRunner implements ApplicationRunner {
@Autowired
private DataSource dataSource;
@Override
public void run(ApplicationArguments args) throws Exception {
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.setLocations("sql");
flyway.setBaselineOnMigrate(true);
flyway.setOutOfOrder(true);
flyway.migrate();
}
}
(7)最后的最后,发布finish事件
建议大家去看一下源码,我去,好复杂啊,有时间,我会把整个流程再完善一下!!!
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
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, analyzers, ex);
throw new IllegalStateException(ex);
}
}
下面说一下BeanFactoryPostProcessor中方法postProcessBeanFactory和BeanPostProcessor中的postProcessBeforeInitialization和postProcessAfterInitialization方法的执行顺序,我们知道beanDefinition生成bean的过程分三个步骤,根据构造方法初始化实例、填充内部属性/依赖、执行初始化方法(即若实现了InitializingBean接口,重写其中的afterPropertiesSet方法,或在xml <bean init-method>指出了初始化方法,afterPropertiesSet在init-method指定的方法之前执行)
1、BeanFactoryPostProcessor中方法postProcessBeanFactory是在beanFactory初始化完成,在refreshContext中调用的,在上述方法中最先执行
2、BeanPostProcessor中的postProcessBeforeInitialization是在bean填充属性完成,初始化之前执行
3. postProcessAfterInitialization在初始化之后执行
@postConstruct也是依赖BeanPostProcessor的postProcessAfterInitialization方法实现的
static方法块在postProcessBeanFactory之后,构造方法之前执行,也很好理解,当创建bean时,刚加载了类还没有执行构造方法就已经执行了static方法块