共计 6636 个字符,预计需要花费 17 分钟才能阅读完成。
思维导图
前言
SpringBoot 一开始最让我印象粗浅的就是通过一个启动类就能启动利用。在 SpringBoot 以前,启动利用尽管也不麻烦,然而还是有点繁琐,要打包成 war 包,又要配置 tomcat,tomcat 又有一个 server.xml 文件去配置。
然而 SpringBoot 则内置了 tomcat,通过启动类启动,配置也集中在一个 application.yml 中,几乎不要太难受。好奇心驱动,于是我很想搞清楚启动类的启动过程,那么开始吧。
《2020 最新 Java 根底精讲视频教程和学习路线!》
一、启动类
首先咱们看最常见的启动类写法。
@SpringBootApplication
public class SpringmvcApplication {public static void main(String[] args) {SpringApplication.run(SpringmvcApplication.class, args);
}
}
把启动类合成一下,实际上就是两局部:
- @SpringBootApplication 注解
- 一个 main()办法,外面调用 SpringApplication.run()办法。
二、@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 {}
很显著,@SpringBootApplication 注解由三个注解组合而成,别离是:
- @ComponentScan
- @EnableAutoConfiguration
- @SpringBootConfiguration
2.1 @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {}
这个注解的作用是 通知 Spring 扫描哪个包上面类,加载符合条件的组件 (比方贴有 @Component 和 @Repository 等的类) 或者 bean 的定义。
所以有一个 basePackages 的属性,如果默认不写,则从申明 @ComponentScan 所在类的 package 进行扫描。
所以 启动类最好定义在 Root package 下,因为个别咱们在应用 @SpringBootApplication 时,都不指定 basePackages 的。
2.2 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
这是一个复合注解,看起来很多注解,实际上要害在 @Import 注解,它会加载 AutoConfigurationImportSelector 类,而后就会触发这个类的 selectImports()办法。依据返回的 String 数组 (配置类的 Class 的名称) 加载配置类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// 返回的 String[]数组,是配置类 Class 的类名
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 返回配置类的类名
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
咱们始终点上来,就能够找到最初的幕后英雄,就是 SpringFactoriesLoader 类,通过 loadSpringFactories()办法加载 META-INF/spring.factories 中的配置类。
这里应用了 spring.factories 文件的形式加载配置类,提供了很好的扩展性。
所以 @EnableAutoConfiguration 注解的作用其实就是开启主动配置,主动配置次要则依附这种加载形式来实现。
2.3 @SpringBootConfiguration
@SpringBootConfiguration 继承自 @Configuration,二者性能也统一,标注以后类是配置类,并会将以后类内申明的一个或多个以 @Bean 注解标记的办法的实例纳入到 spring 容器中,并且实例名就是办法名。
2.4 小结
咱们在这里画张图把 @SpringBootApplication 注解蕴含的三个注解别离解释一下。
三、SpringApplication 类
接下来讲 main 办法里执行的这句代码,这是 SpringApplication 类的静态方法 run()。
// 启动类的 main 办法
public static void main(String[] args) {SpringApplication.run(SpringmvcApplication.class, args);
}
// 启动类调的 run 办法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 调的是上面的,参数是数组的 run 办法
return run(new Class<?>[] {primarySource}, args);
}
// 和下面的办法区别在于第一个参数是一个数组
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 实际上 new 一个 SpringApplication 实例,调的是一个实例办法 run()
return new SpringApplication(primarySources).run(args);
}
通过下面的源码,发现实际上最初调的并不是静态方法,而是实例办法,须要 new 一个 SpringApplication 实例,这个结构器还带有一个 primarySources 的参数。所以咱们间接定位到结构器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// 断言 primarySources 不能为 null,如果为 null,抛出异样提醒 Assert.notNull(primarySources, "PrimarySources must not be null");
// 启动类传入的 Class this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断以后我的项目类型,有三种:NONE、SERVLET、REACTIVE this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置 ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 判断主类,初始化入口类 this.mainApplicationClass = deduceMainApplicationClass();}
// 判断主类 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;
}
以上就是创立 SpringApplication 实例做的事件,上面用张图来示意一下。
创立了 SpringApplication 实例之后,就实现了 SpringApplication 类的初始化工作,这个实例里包含监听器、初始化器,我的项目利用类型,启动类汇合,类加载器。如图所示。
失去 SpringApplication 实例后,接下来就调用实例办法 run()。持续看。
public ConfigurableApplicationContext run(String... args) {
// 创立计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
// 定义上下文对象
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//Headless 模式设置
configureHeadlessProperty();
// 加载 SpringApplicationRunListeners 监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发送 ApplicationStartingEvent 事件
listeners.starting();
try {
// 封装 ApplicationArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 配置环境模块
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 依据环境信息配置要疏忽的 bean 信息
configureIgnoreBeanInfo(environment);
// 打印 Banner 标记
Banner printedBanner = printBanner(environment);
// 创立 ApplicationContext 利用上下文
context = createApplicationContext();
// 加载 SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class}, context);
//ApplicationContext 根本属性配置
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文
refreshContext(context);
// 刷新后的操作,由子类去扩大
afterRefresh(context, applicationArguments);
// 计时完结
stopWatch.stop();
// 打印日志
if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发送 ApplicationStartedEvent 事件,标记 spring 容器曾经刷新,此时所有的 bean 实例都曾经加载结束
listeners.started(context);
// 查找容器中注册有 CommandLineRunner 或者 ApplicationRunner 的 bean,遍历并执行 run 办法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 发送 ApplicationFailedEvent 事件,标记 SpringBoot 启动失败
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发送 ApplicationReadyEvent 事件,标记 SpringApplication 曾经正在运行,即曾经胜利启动,能够接管服务申请。listeners.running(context);
}
catch (Throwable ex) {
// 报告异样,然而不发送任何事件
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
联合正文和源码,其实很清晰了,为了加深印象,画张图看一下整个流程。
总结
外表启动类看起来就一个 @SpringBootApplication 注解,一个 run()办法。其实是通过高度封装后的后果。咱们能够从这个剖析中学到很多货色。比方应用了 spring.factories 文件来实现主动配置,进步了扩展性。在启动时应用观察者模式,以事件公布的模式告诉,升高耦合,易于扩大等等。
那么 SpringBoot 的启动类剖析就讲到这里了,感激大家的浏览。
感觉有用就点个赞吧,你的点赞是我创作的最大能源~
我是一个致力让大家记住的程序员。咱们下期再见!!!