任何一个Spring Boot项目,都会用到如下的启动类
@SpringBootApplication
public class SpringboootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringboootdemoApplication.class, args);
}
}
主要的两行代码是@SpringBootApplication和SpringApplication.run,下面从这两个点来分析Spring boot启动过程。
1. @SpringBootApplication
@SpringBootApplication注解的源码点击去查看,发现在他内部使用了下面三个注解,在上一节简单的提到过。下面逐个分析。
@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 {
...
}
- @Configuration(@SpringBootConfiguration内部应用了@Configuration)
- @EnableAutoConfiguration
- @ComponentScan
当然这三个注解也可以替代@SpringBootApplication注解,只是不方便而已。
@Configuration:用@Configuration注解该类,等价 与XML中配置beans;用@Bean标注方法等价于XML中配置bean。任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。
@ComponentScan:功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
@EnableAutoConfiguration:
而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已。具体流程是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
该接口的定义为:
@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 {};
}
2.SpringApplication启动流程
首先进入run()方法
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);
}
run方法中去创建了一个SpringApplication实例,并且又调用了run(String… args)方法,下面分为两步分分析,一是创建SpringApplication实例的过程,也叫初始化过程。二是启动过程也就是run(String… args)做了哪些事情。
构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; //1
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//2
this.webApplicationType = deduceWebApplicationType();//3
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));//4
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//5
this.mainApplicationClass = deduceMainApplicationClass();//6
}
- 在源码中调用此方法第一个参数传入的null,把null赋值给了成员变量resourceLoader。
- 首先primarySources是一个Set类型的成员变量,它存放的是Class类型的对象,我们在启动时把SpringboootdemoApplication.class传入
WebApplicationType是一个枚举类型,它有三种类型
- NONE:应用程序不应该作为Web应用程序运行,不应该启动嵌入式Web服务器。
- SERVLET:应用程序应作为基于servlet的Web应用程序运行,并应启动嵌入式servlet Web服务器。
- REACTIVE:应用程序应作为响应式Web应用程序运行,并应启动嵌入式响应式Web服务器。
deduceWebApplicationType就是根据当前的环境判断处,并返回webApp的类型
通过反射实例化了ApplicationContextInitializer,并且把对象添加到initializers中,其中initializers是List结构,它保存了ApplicationContextInitializer的实例。
- 和4中同理,保存程序事件监听器ApplicationListener
找出启动类,设置到 mainApplicationClass 中
private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
构造方法的分析就结束了,总结一下就是对一些成员变量的初始化。下面对run方法的分析。
run
public ConfigurableApplicationContext run(String... args) {
//构造一个任务执行观察器
StopWatch stopWatch = new StopWatch();
stopWatch.start();// 开始执行,记录开始时间
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//创建一个 DefaultApplicationArguments 对象,它持有 args 参数,就是 main 函数传进来的参数调用 prepareEnvironment 方法。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//创建上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
总结
SpringBoot启动的时候,不论调用什么方法,都会构造一个SpringApplication的实例,然后调用这个实例的run方法,这样就表示启动SpringBoot。
在run方法调用之前,也就是构造SpringApplication的时候会进行初始化的工作,初始化的时候会对SpringApplication属性进行赋值,其中包括ApplicationContextInitializer、ApplicationListener、WebApplicationType 、mainApplicationClass。
SpringApplication构造完成之后调用run方法,启动SpringApplication,run方法执行的时候会做以下几件事:
- 构造一个StopWatch,观察SpringApplication的执行
- 找出所有的SpringApplicationRunListener并封装到SpringApplicationRunListeners中,用于监听run方法的执行。监听的过程中会封装成事件并广播出去让初始化过程中产生的应用程序监听器进行监听
- 构造Spring容器(ApplicationContext),并返回对应类型的AppContext
- 初始化过程中产生的初始化器在这个时候开始工作
- Spring容器的刷新(完成bean的解析、各种processor接口的执行、条件注解的解析等等)
- 从Spring容器中找出ApplicationRunner和CommandLineRunner接口的实现类并排序后依次执行