目前后端最常用的框架就是springboot,今天就稍微来研究一下springboot的启动流程。
要启动一个springboot项目,最常用的就是以下的代码。
java
@SpringBootApplication
public class SpringBootDemo {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemo.class, args);
}
}
几乎所有的springboot项目都是这样启动的吧。这里最关键的地方就是两点,@SpringBootApplication
注解和SpringApplication.run()
。
@SpringBootApplication原理
网上已经有了很多的文章介绍它,@SpringbootApplication
本质上是很多个注解的组合。
java
@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}
)}
)
这里实际上最重要的只有三个注解,分别是@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
。
@SpringBootConfiguration
@SpringBootConfiguration
本身也是由几个注解构成的。
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
除去Java的元注解,实际上最重要的就是@Configuration
注解了。这个注解基本上大家在每个项目应该都有用到过。springboot可以不写xml配置,靠的就是@Configuration
注解。
它的作用就是声明当前类为配置类,并且可以将类的实例方法中加上了@Bean
注解的返回对象也一并加入到IoC容器中。
java
@Configuration
public class OpenApiConfiguration {
@Bean
public OpenAPI springOpenAPI() {
return new OpenAPI()
.info(new Info().title("springboot")
.description("springboot")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.components(new Components()
.addSecuritySchemes("Authorization", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("Authorization")))
// AddSecurityItem section applies created scheme globally
.addSecurityItem(new SecurityRequirement().addList("Authorization"));
}
}
以上的代码就将OpenApiConfiguration
作为配置类,springboot
会调用springOpenAPI()
函数,将OpenAPI()
对象注入进IoC容器。
@ComponentScan
@ComponentScan
的作用就是扫描当前的包,以及子包,将所有的带有springboot相关的注解(@Component
,@Service
,@Controller
等)的类注入到IoC容器中,等待之后使用。
如果@ComponentScan不指定basePackages,那么默认扫描当前包,所以一般都是把SpringBootApplication.class放在最外层,以便扫描所有的类。
@EnableAutoConfiguration
@EnableAutoConfiguration
也是由几个注解一起实现的。
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
其中最为重要的就是@Import
这个注解,上面的@AutoConfigurationPackage
进入之后也是一个@Import
注解,只不过导入了不同的类。
最关键的就是 @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector
实现了ImportSelector
接口,里面有一个selectImports
函数。
java
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
在AutoConfigurationImportSelector
里的实现是这样的
java
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
其中最重要的就是this.getAutoConfigurationEntry(annotationMetadata)
这行
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.<String>removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
其中有一个行List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
继续跟踪可以看到
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
这里就来到了springboot最核心的机制,读取classpath下的META-INF/spring.factories
文件,然后将key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的类通过反射实例化,然后注入到IoC容器中。
比如一下是安全框架shiro-spring-boot-starter
的spring.factories文件
java
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\
org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration,\
org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration,\
org.apache.shiro.spring.boot.autoconfigure.ShiroBeanAutoConfiguration,\
org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration,\
org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer = \
org.apache.shiro.spring.boot.autoconfigure.ShiroNoRealmConfiguredFailureAnalyzer
org.springframework.boot.env.EnvironmentPostProcessor=\
org.apache.shiro.spring.config.web.autoconfigure.ShiroEnvironmentPostProcessor
接下来我们就可以分析一下run方法了。
SpringApplication.run()
run()方法的源码如下
java
public ConfigurableApplicationContext run(String... args) {
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdowHookAddition();
}
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
if (context.isRunning()) {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
首先是调用了createBootstrapContext
初始化了DefaultBootstrapContext
对象。
随后设置headless模式,也就是电脑没有显示屏,没有键盘鼠标的情况。
java
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
当系统属性中java.awt.headless
为true的时候就为headless模式。
java
System.setProperty("java.awt.headless","true")
接下来就是获取监听器并启动
java
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
在getRunListeners(args)
中,getSpringFactoriesInstances
会获取所有的监听器,调用SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver)
,从"META-INF/spring.factories"
文件里找所有的listener,最后返回SpringApplicationRunListeners
实例。
在这里springboot会提供一个EventPublishingRunListener ,它实现了SpringApplicationRunListener 接口,springboot会使用这个类发布一个ApplicationContextInitializedEvent 事件,通过定义ApplicationListener就可以监听这个事件。
之后调用listeners.starting 方法,调用doWithListeners
方法
java
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
进入listener.starting(bootstrapContext)方法中,这里是一个接口,实际执行的就是上文提到的EventPublishingRunListener对应的方法。
java
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
再进入multicastInitialEvent方法
java
private void multicastInitialEvent(ApplicationEvent event) {
refreshApplicationListeners();
this.initialMulticaster.multicastEvent(event);
}
这里的refreshApplicationListeners()就会循环所有上面加载到的listeners,调用this.initialMulticaster::addApplicationListener
将listener添加到applicationListeners集合中。
java
private void refreshApplicationListeners() {
this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}
完成之后进入下一步this.initialMulticaster.multicastEvent
。
这一步就是去执行每一个listener具体的函数,调用invokeListener
方法。最后调用到每个listener的onApplicationEvent
方法。具体到每个listener的实现,比如LoggingApplicationListener
java
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent startingEvent) {
onApplicationStartingEvent(startingEvent);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
}
else if (event instanceof ApplicationPreparedEvent preparedEvent) {
onApplicationPreparedEvent(preparedEvent);
}
else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
当所有的listener执行完毕,最后将会回到doWithListeners
。最后回到run(args)方法。
接着往下走就进入了try里面
java
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
获取命令行参数,这一步将会对jar启动最后的程序命令行参数进行封装。
然后准备环境变量,读取配置信息(bootstrap.yml,application.yml)
java
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
这一步读取配置信息是通过EventPublishingRunListener
,发布一个ApplicationEnvironmentPreparedEvent
事件,将会有一个EnvironmentPostProcessorApplicationListener
来对事件进行处理,并进行application.yml的解析,并添加到Environment中。
java
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 根据不同的类型创建不同实现的Environment对象,读取环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 将命令行参数设置到环境变量中
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 发布ApplicationEnvironmentPreparedEvent
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 将所有spring.main 开头的配置信息绑定到SpringApplication中
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 更新PropertySources配置
ConfigurationPropertySources.attach(environment);
return environment;
}
下一步是打印SpringBoot的Banner,springboot启动时的字符画
然后创建上下文容器
java
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
进入prepareContext
java
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
addAotGeneratedInitializerIfNecessary(this.initializers);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
if (!AotDetector.useGeneratedArtifacts()) {
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
}
listeners.contextLoaded(context);
}
首先获取所有的ApplicationContextInitializer,调用initialize方法。然后初始化BeanFactory,检查是否有相同的bean,是否开启循环引用,将启动类注入到Spring容器中。
然后进入刷新spring容器方法
java
refreshContext(context);
这里将会对上下文做一些初始化,调用BeanFactoryPostProcessors
,注册BeanPostProcessors
。然后发布ContextRefreshedEvent
事件。
java
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
之后就是打印启动时间,发布ApplicationStartedEvent
事件。
最后获取Spring容器中ApplicationRunner
/CommandLineRunner
类型的Bean,并执行run方法。
java
callRunners(context, applicationArguments);
至此springboot基本启动完成,发布ApplicationReadyEvent
事件,正式开始运行。