思维导图

前言

SpringBoot一开始最让我印象粗浅的就是通过一个启动类就能启动利用。在SpringBoot以前,启动利用尽管也不麻烦,然而还是有点繁琐,要打包成war包,又要配置tomcat,tomcat又有一个server.xml文件去配置。

然而SpringBoot则内置了tomcat,通过启动类启动,配置也集中在一个application.yml中,几乎不要太难受。好奇心驱动,于是我很想搞清楚启动类的启动过程,那么开始吧。

《2020最新Java根底精讲视频教程和学习路线!》

一、启动类

首先咱们看最常见的启动类写法。

@SpringBootApplicationpublic 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的启动类剖析就讲到这里了,感激大家的浏览。

感觉有用就点个赞吧,你的点赞是我创作的最大能源~

我是一个致力让大家记住的程序员。咱们下期再见!!!