28.SpringBoot入口类原理解析
1.SpringBoot入口类
通过Spring Initializr新建项目,SpringBoot会自动生成如下所示的入口启动类。
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
2.@SpringBootApplication原理
@SpringBootApplication开启了Spring的组件扫描和SpringBoot自动配置功能。实际上,它是一个复合注解,包含3个重要的注解@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。在SpringBoot早期版本中,需要在入口类中同时添加这3个注解,但是从1.2.0版本之后,只需要在入口类添加@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
- @SpringBootConfiguration:表明该类使用Spring基于Java的注解,SpringBoot推荐使用基于Java的注解而不是XML配置。查看@SpringBootConfiguration的源代码可知,其是对@Configuration进行的简单包装,然后取名为SpringBootConfiguration。
- @EnableAutoConfiguration:开启自动配置的功能。查看@EnableAutoConfiguration的源代码可知,其包含@Import注解。@Import注解的主要作用是借助EnableAutoConfigurationImportSelector将SpringBoot应用所有符合条件的@Configuration配置加载到当前SpringBoot创建并使用的IOC容器中(Spring应用程序上下文ApplicationContext)。Spring框架提供了很多@Enable开头的注解,这些注解都是借助@Import的支持,来收集和注册特定场景相关的bean定义。
- @ComponentScan:启动组件扫描注解,开发的组件或bean定义能自动发现并注入到Spring应用程序上下文。如控制层注解@Controller、服务层注解@Service和@Component等,这些注解都可以被@ComponentScan注解扫描到。
3.SpringApplication的run方法
除了@SpringBootConfiguration注解,入口类中还有一个重要的内容是SpringApplication.run方法。在run方法中,首先创建一个SpringApplication的对象实例,然后调用SpringApplication的run方法,其源码如下所示。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//开启监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
//创建应用上下文
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//准备应用上下文
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新应用上下文
this.refreshContext(context);
//刷新后操作
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
从源代码中可以看出,SpringBoot首先开启了一个SpringApplicationRunListeners监听器,然后通过createApplicationContext、prepareContext和refreshContext方法创建、准备和刷新应用上下文ConfigurableApplicationContext,通过应用上下文加载应用所需要的类和各种配置环境等,最后启动一个应用实例。
4.SpringApplicationRunListeners监听器
SpringApplicationRunListeners接口规定了SpringBoot的生命周期,在各个生命周期广播相应的ApplicationEvent事件,调用实际的是ApplicationListener,其源代码如下所示。
public interface SpringApplicationRunListener {
//执行run方法时触发
default void starting() {
}
//环境创建完成时触发
default void environmentPrepared(ConfigurableEnvironment environment) {
}
//上下文建立完成时触发
default void contextPrepared(ConfigurableApplicationContext context) {
}
//上下文载入配置时触发
default void contextLoaded(ConfigurableApplicationContext context) {
}
default void started(ConfigurableApplicationContext context) {
}
default void running(ConfigurableApplicationContext context) {
}
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
ApplicationListener是Spring框架对Java中监听器模式的一种框架实现,其源代码如下所示。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
ApplicationListener接口只有一个方法onApplicationEvent。如果在上下文中部署实现了一个ApplicationListener接口的监听器,每当ApplicationEvent事件发布到ApplicationContext时,该监听器就会得到通知。如果要为SpringBoot应用添加自定义的ApplicationListener,可通过SpringApplication.addListeners()或者SpringApplication.setListeners()方法添加一个或者多个自定义的ApplicationListener。
5.ApplicationContextInitializer接口
在SpringBoot准备上下文prepareContext的时候,会对ConfigurableApplicationContext实例做进一步的设置或处理,prepareContext源码如下所示。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
//对上下文进行设置和处理
this.applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
在准备上下文prepareContext方法中,通过applyInitializers方法对context上下文进行设置和处理,applyInitializers的源码如下所示。
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
在applyInitializers方法中,主要是调用ApplicationContextInitializer的initialize方法对应用上下文进行设置和处理。ApplicationContextInitializer本质上是一个回调接口,用于在ConfigurableApplicationContext执行refresh操作之前对它进行一些初始化操作。一般情况下,开发者无需自定义一个ApplicationContextInitializer,如果需要自定义一个ApplicationContextInitializer,则可以通过SpringApplication.addInitializers()设置。
6.ApplicationRunner与CommandLineRunner
ApplicationRunner与CommandLineRunner接口执行点是在容器启动成功后的最后一步回调,对应的源码分别如下所示。
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}
在ApplicationRunner与CommandLineRunner类中,只有一个run方法,但它们的入参不一样,分别是ApplicationArguments和可变String数组。如果有多个ApplicationRunner与CommandLineRunner实现类,并且需要按照一定的顺序执行时,可以通过在实现类上添加@Order(value=整数值)来实现,那么SpringBoot就会按照@Order中value值从小到大依次执行。
如果想再SpringBoot启动的时候完成一些特定的代码,则可以通过实现ApplicationRunner与CommandLineRunner接口来完成,这两个接口实现方式一样,如实现CommandLineRunner的接口。
public class MyCommandRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception{
//todo
}
}
本文地址:https://blog.csdn.net/Jgx1214/article/details/107328642
上一篇: Java各种数据类型互相转换
推荐阅读
-
spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
-
Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
Mybaits 源码解析 (三)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
Android中实现「类方法指令抽取方式」加固方案原理解析
-
Laravel框架源码解析之入口文件原理分析
-
Java枚举类接口实例原理解析
-
28.SpringBoot入口类原理解析
-
Java类加载原理解析
-
Java Method类及invoke方法原理解析
-
Python Request类源码实现方法及原理解析