springboot是基于spring的新型的轻量级框架,最厉害的地方当属自动配置。那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置
用过springboot的技术人员很显而易见的两者之间的差别就是视觉上很直观的:springboot有自己独立的启动类(独立程序)
@SpringBootApplication
public class SpringbootWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebApplication.class, args);
}
}
从启动类看,主要是两部分,一个是@SpringBootApplication注解,另一个是SpringApplication.run方法,下面就看这两个地方。
@Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
在其中比较重要的有三个注解,分别是:
1)@SpringBootConfiguration
Spring Boot的配置类;标注在某个类上,表示一个类提供了Spring Boot应用程序
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@Configuration:配置类上来标注这个注解;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
注意:
配置类相当于配置文件;配置类也是容器中的一个组件,它使用了@Component这个注解。
2)@EnableAutoConfiguration
告诉SpringBoot开启自动配置功能,这样自动配置才能生效
借助@import,扫描并实例化满足条件的自动配置的bean,然后加载到IOC容器中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
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 {};
}
@AutoConfigurationPackage:自动配置包
@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件
使用@EnableAutoConfiguration
这个注解开启自动扫描,然后使用select选择挑选满足条件的文件,并且使用SpringFactoriesLoader进行实例化。最后加载到IOC容器里面,即ApplicationContext中。
3)@ComponentScan
@ComponentScan就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IOC容器中去 。
关于springboot的自动配置原理,我在另外一篇文章有详细的源码解析,以上三个注解这里就不细说了,可以看一下这篇文章:Spring Boot的自动配置原理
我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
创建SpringApplication (new SpringApplication)
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
// 初始化资源加载器
this.resourceLoader = resourceLoader;
// 资源加载类不能为 null
Assert.notNull(primarySources, "PrimarySources must not be null");
// 初始化加载资源类集合并去重
// 将启动类放入primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用程序是不是web应用
// 根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
// 设置初始化器(Initializer)
// 就是去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
//就是去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断出主应用入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
在推断应用程序是不是web应用的时候调用了deduceFromClasspath() 方法
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
// springboot2.0提出的响应式web应用
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
// 如果两个包路径都没有的话,就是普通应用
if (!ClassUtils.isPresent(className, null)) {
// 普通的应用
return WebApplicationType.NONE;
}
}
// 其实最后返回的就是这个servlet,因为是web应用
return WebApplicationType.SERVLET;
}
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
initializers 是 SpringApplication 中的一个实例属性
public void setInitializers(Collection extends ApplicationContextInitializer>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
initailizer实现了ApplicationContextInitializer接口
@FunctionalInterface
public interface ApplicationContextInitializer {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
ApplicationContextInitializer接口的作用,在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。
调用initialize()方法,把初始化的ApplicationContextInitializer实现加载到SpringApplication中
通过getSpringFactoriesInstances(
ApplicationContextInitializer.class)方法获得实现类
private Collection getSpringFactoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class>[] {});
}
private Collection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 使用 Set保存names
Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names进行实例化
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对实例进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
其中SpringFactoriesLoader.loadFactoryNames(type, classLoader)和自动配置一样读取META-INF/spring.factories然后拿到key为org.springframework.context.ApplicationContextInitializer的集合
其中一个spring.factories文件
获取到的所有ApplicationContextInitializer
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
同理,也是一样去读取META-INF/spring.factories然后拿到key为org.springframework.context.ApplicationListener的集合
其中一个spring.factories文件
获取到的所有ApplicationListener
private Class> deduceMainApplicationClass() {
try {
// 堆栈信息--调用链,执行链
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
// 通过main的栈帧推断出入口类的名字
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
总结:
1. 获取启动类
2.获取web应用类型
3.读取了对外扩展的ApplicationContextInitializer ,ApplicationListener
4. 根据main推算出所在的类
就是去初始化了一些信息
public ConfigurableApplicationContext run(String... args) {
// 记时器,统计应用启动的时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
ConfigurableApplicationContext context = null;
// 开启了Headless模式
configureHeadlessProperty();
// 去spring.factroies中读取了SpringApplicationRunListener 的组件, 就是用来发布事件或者运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布ApplicationStartingEvent事件,在运行开始时发送
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 根据命令行参数 实例化一个ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// 打印器,springboot启动的时候会打印springboot的标志以及对应的版本
Banner printedBanner = printBanner(environment);
// 根据webApplicationType创建Spring上下文
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//预初始化spring上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 加载spring ioc 容器 **相当重要 由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
// 加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh
refreshContext(context);
// Spring上下文后置处理
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布应用上下文启动完成事件
listeners.started(context);
// 执行所有 Runner 运行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布应用上下文就绪事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
开启计时器,用来统计应用启动的时间
public void start() throws IllegalStateException {
// 传入一个空字符串作为当前任务的名称
this.start("");
}
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
// 如果当前任务名字不为空,抛出异常
throw new IllegalStateException("Can't start StopWatch: it's already running");
} else {
// 否则,记录当前任务的开始时间
this.currentTaskName = taskName;
this.startTimeNanos = System.nanoTime();
}
}
private SpringApplicationRunListeners getRunListeners(String[] args) {
// 类加载对应的监听器
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
// 创建SpringApplicationRunListener实例
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
getSpringFactoriesInstances同样又是去读取META-INF/spring.factories然后拿到key为SpringApplicationRunListener的集合,并实例化
SpringApplicationRunListener作用就是用来发布事件或者说运行监听器
这里拿到的就是EventPublishingRunListener
然后执行starting方法,发布ApplicationStartingEvent事件
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
getApplicationListeners拿到所有监听ApplicationStartingEvent的监听器
之后会调用监听器,最后执行监听器的onApplicationEvent(event)方法
其实就是告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
根据命令行参数 实例化一个ApplicationArguments
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 将命令行参数读取环境变量中
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
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);
// 如果不是web应用环境,将环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//更新PropertySources
ConfigurationPropertySources.attach(environment);
return environment;
}
listeners.environmentPrepared(bootstrapContext, environment);
发布了ApplicationEnvironmentPreparedEvent 的事件
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
告诉SpringApplicationRunListener的监听器:“当前SpringBoot应用使用的Environment准备好了咯!”。
其中有一个EnvironmentPostProcessorApplicationListener的
onApplicationEnvironmentPreparedEvent方法中读取了全局配置文件(application.yml)
我看的源码是2.5.4版本的,之前2.3.6版本的应该是ConfigFileApplicationListener
protected ConfigurableApplicationContext createApplicationContext() {
// 首先进行判断有没有指定的实现类
Class> contextClass = this.applicationContextClass;
// 如果没有,则根据应用类型选择
if (contextClass == null) {
try {
// 根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
// 通过反射,得到创建的对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
对于Web应用,上下文类型是DEFAULT_WEB_CONTEXT_CLASS。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 拿到之前读取到所有ApplicationContextInitializer的组件, 循环调用initialize方法
applyInitializers(context);
// 发布了ApplicationContextInitializedEvent
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 获取当前spring上下文beanFactory (负责创建bean)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 在Spring下 如果出现2个重名的bean, 则后读取到的会覆盖前面
// 在SpringBoot 在这里设置了不允许覆盖, 当出现2个重名的bean 会抛出异常
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 设置当前spring容器是不是要将所有的bean设置为懒加载
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set
加载spring ioc 容器 **相当重要
由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器
所以springboot对它做了扩展:
加载自动配置类:invokeBeanFactoryPostProcessors 方法
创建servlet容器:onRefresh方法
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
最终会走到spring加载ioc的refresh()方法,看过spring源码的肯定知道这个方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
在Spring容器刷新上下文后进行调用,依次调用注册的Runners。
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
做计时监听器停止操作,并统计一些任务执行信息
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
} else {
long lastTime = System.nanoTime() - this.startTimeNanos;
this.totalTimeNanos += lastTime;
this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.currentTaskName = null;
}
}
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
触发所有 SpringApplicationRunListener 监听器的 running 事件的方法。
总结:
1. 初始化SpringApplication 从spring.factories 读取 listener ApplicationContextInitializer 。
2.运行run方法
3.读取 环境变量 配置信息.....
4. 创建springApplication上下文:ServletWebServerApplicationContext
5. 预初始化上下文 : 读取启动类
6.调用refresh 加载ioc容器
加载所有的自动配置类
创建servlet容器
7.在这个过程中springboot会调用很多监听器对外进行
SpringBoot 事件监听器发布顺序:(官网也有)
1.ApplicationStartingEvent在运行开始时发送,但在进行任何处理之前(侦听器和初始化程序的注册除外)发送。
2.在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent。
3.准备ApplicationContext并调用ApplicationContextInitializers之后,将发送ApplicationContextInitializedEvent。
4.读取完配置类后发送ApplicationPreparedEvent。
5.在刷新上下文之后但在调用任何应用程序和命令行运行程序之前,将发送ApplicationStartedEvent。
6.紧随其后发送带有LivenessState.CORRECT的AvailabilityChangeEvent,以指示该应用程序被视为处于活动状态。
7.在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。
8.紧随其后发送ReadabilityState.ACCEPTING_TRAFFIC的AvailabilityChangeEvent,以指示应用程序已准备就绪,可以处理请求。
9.如果启动时发生异常,则发送ApplicationFailedEvent。
参考文章:
SpringBoot启动原理及相关流程
深入剖析Springboot启动原理的底层源码,再也不怕面试官问了!