Spring Boot系列——Spring Boot如何启动

Spring Boot启动过程

​上篇《Spring Boot系列——5分钟构建一个应用》介绍了如何快速创建一个Spring Boot项目并运行。虽然步骤少流程简单,为开发者省去了很多重复性的配置工作,但是其底层实现并没有这么简单。

这篇,我们就通过入口类TutorialApplication看看Spring Boot是如何启动的。

注解

写过Spring Boot都知道需要有一个入口类,就是本例子中的TutorialApplication,而这个类上面必不可上的需要有一个@SpringBootApplication注解。

点击进入该注解,我们可以发现其是一个复合注解,包括@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.2.0
 */
@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 {

   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
   @AliasFor(annotation = EnableAutoConfiguration.class)
   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
    */
   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};

   /**
    * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
    * for a type-safe alternative to String-based package names.
    * @return base packages to scan
    * @since 1.3.0
    */
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};

   /**
    * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
    * scan for annotated components. The package of each class specified will be scanned.
    * <p>
    * Consider creating a special no-op marker class or interface in each package that
    * serves no purpose other than being referenced by this attribute.
    * @return base packages to scan
    * @since 1.3.0
    */
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};

}

@SpringBootConfiguration

该注解底层其实就是@Configuration注解。熟悉Spring的发展里程碑就知道这是Java Config的配置形式。

通过该注解修饰,表示该类是一个配置类。

@EnableAutoConfiguration

该注解其实也是一个复合注解。

/**
 * Enable auto-configuration of the Spring Application Context, attempting to guess and
 * configure beans that you are likely to need. Auto-configuration classes are usually
 * applied based on your classpath and what beans you have defined. For example, if you
 * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
 * {@link TomcatServletWebServerFactory} (unless you have defined your own
 * {@link ServletWebServerFactory} bean).
 * <p>
 * When using {@link SpringBootApplication}, the auto-configuration of the context is
 * automatically enabled and adding this annotation has therefore no additional effect.
 * <p>
 * Auto-configuration tries to be as intelligent as possible and will back-away as you
 * define more of your own configuration. You can always manually {@link #exclude()} any
 * configuration that you never want to apply (use {@link #excludeName()} if you don't
 * have access to them). You can also exclude them via the
 * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
 * after user-defined beans have been registered.
 * <p>
 * The package of the class that is annotated with {@code @EnableAutoConfiguration},
 * usually via {@code @SpringBootApplication}, has specific significance and is often used
 * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
 * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
 * not using {@code @SpringBootApplication}) in a root package so that all sub-packages
 * and classes can be searched.
 * <p>
 * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
 * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
 * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
 * often using {@link ConditionalOnClass @ConditionalOnClass} and
 * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @see ConditionalOnBean
 * @see ConditionalOnMissingBean
 * @see ConditionalOnClass
 * @see AutoConfigureAfter
 * @see SpringBootApplication
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   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 {};

}

其实现也是通过类似@Import的方式注入AutoConfigurationImportSelector类,并借助该类将所有符合条件的Configuration注解修饰的配置类加载到Spring Boot容器中。从classpath中搜索所有的META-INF/spring.factories配置文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应配置项通过反射的形式实例化为标注了@Configuration和javaconfig形式的IOC容器配置类,然后汇总为一个并加载到ioc容器中。

@ComponentScan

这个注解就不需要多介绍了吧,其作用自动扫描加载符合条件的bean。

SpringApplication

从项目的入口第一个碰到的就是SpringApplication类。

@SpringBootApplication
public class TutorialApplication {

    public static void main(String[] args) {
        SpringApplication.run(TutorialApplication.class, args);
    }
}

进入该类的静态方法run,可以看到其在构造SpringApplication对象

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
   return new SpringApplication(primarySources).run(args);
}

进入SpringApplication构造方法,可以看到

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = deduceWebApplicationType();
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

主要做了如下几件事:

  • 加载Source,这里只有只有Application
  • 推断WebApplicationType,该枚举有三种类型NONE、SERVLET、REACTIVE。
  • 设置初始化器变量setInitializers,初始化后得到6个初始化变量,这些类在上面提到的spring.factories中可以找到
  • 设置监听器,与上面setInitializers实现类似,最终得到如下10个listeners
  • 最后推断带有main函数的所在类,即入口类,这里就是TutorialApplication
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方法

看完SpringApplication是如何初始化后,我们来看下这个后面的run方法具体做了哪些事。

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      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;
}
  • StopWatch,这是一个spring-core中的工具类,用来给程序运行计时(对于经常遇到需要计算某方法或接口耗时的场景,这个比System.currentTimeMillis好用)
  • configureHeadlessProperty配置,设置系统属性 java.awt.headless,这里设置为 true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。
  • 遍历listeners并启动
  • 封装入参args为AppliationArguments对象
  • 打印banner(就是我们启动时看到的spring标识)
  • 后面就是初始化上下文并加载上下文,具体实现就不进去看了

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发与安全

《dive into python3》 笔记摘录

0、In Python 2, the / operator usually meant integer division, but you could make...

28500
来自专栏java、Spring、技术分享

Feign与Spring Cloud源码解析

  在Feign的官方文档上, 我们可以看到Feign最重要的一句话是:Feign makes writing java http clients easier...

56120
来自专栏编程坑太多

JAVA实现Excel导入导出

59330
来自专栏石奈子的Java之路

原 荐 SpringBoot 2.0 系列0

58130
来自专栏不会写文章的程序员不是好厨师

日志那些事儿——slf4j集成logback/log4j

在日志Logger漫谈中提到了slf4j仅仅是作为日志门面,给用户提供统一的API使用,而真正的日志系统的实现是由logback或者log4j这样的日志系统实现...

61730
来自专栏java、Spring、技术分享

Spring Import 三种用法与源码解读

  最近在看Spring Cloud相关的源码,每次引入一个新的starter,发现都会加一些enable的注解,比如:@EnableDiscoveryClie...

45540
来自专栏曾大稳的博客

retrofit 源码分析

loadServiceMethod: 拿到对应的解析器,根据注解解析方法的返回类型,方法参数,网络请求的一系列参数 封装成一个对象

15720
来自专栏javathings

@SpringBootApplication 的作用是什么

@SpringBootApplication 标注的类为 Spring Boot 的主配置类,Spring Boot 会运行这个类的 main 方法来启动 Sp...

1.1K20
来自专栏py+selenium

pip install xxxx报错(一大堆红色exception)【解决】

  File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 215, in main

89910
来自专栏ml

hdu-----2491Priest John's Busiest Day(2008 北京现场赛G)

Priest John's Busiest Day Time Limit: 4000/2000 MS (Java/Others)    Memory Limit...

303100

扫码关注云+社区

领取腾讯云代金券