Spring Boot2.X 启动配置原理

几个重要的事件回调机制

  • 配置在META-INF/spring.factories
    • ApplicationContextInitializer

    • /**
       * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
       * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
       *
       * <p>Typically used within web applications that require some programmatic initialization
       * of the application context. For example, registering property sources or activating
       * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
       * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
       * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
       *
       * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
       * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
       * implemented or if the @{@link org.springframework.core.annotation.Order Order}
       * annotation is present and to sort instances accordingly if so prior to invocation.
       *
       * @author Chris Beams
       * @since 3.1
       * @param <C> the application context type
       * @see org.springframework.web.context.ContextLoader#customizeContext
       * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
       * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
       * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
       */
      public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
      
      	/**
      	 * Initialize the given application context.
      	 * @param applicationContext the application to configure
      	 */
      	void initialize(C applicationContext);
      
      }
      • SpringApplicationRunListener

    • public interface SpringApplicationRunListener {
      
      	/**
      	 * 回调
           * Called once the {@link ApplicationContext} has been created and prepared, but
      	 * before sources have been loaded.
      	 * @param context the application context
      	 */
      	default void contextPrepared(ConfigurableApplicationContext context) {
      	}
      
      	/**
           * 回调
      	 * Called once the application context has been loaded but before it has been
      	 * refreshed.
      	 * @param context the application context
      	 */
      	default void contextLoaded(ConfigurableApplicationContext context) {
      	}
      
      }
  • 只需要放在ioc容器中

    • ApplicationRunner

    • /**
       * Interface used to indicate that a bean should <em>run</em> when it is contained within
       * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
       * within the same application context and can be ordered using the {@link Ordered}
       * interface or {@link Order @Order} annotation.
       *
       * @author Phillip Webb
       * @since 1.3.0
       * @see 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;
      
      }
    • CommandLineRunner

    • /**
       * Interface used to indicate that a bean should <em>run</em> when it is contained within
       * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
       * within the same application context and can be ordered using the {@link Ordered}
       * interface or {@link Order @Order} annotation.
       * <p>
       * If you need access to {@link ApplicationArguments} instead of the raw String array
       * consider using {@link ApplicationRunner}.
       *
       * @author Dave Syer
       * @since 1.0.0
       * @see ApplicationRunner
       */
      @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;
      
      }
    • 测试:只要创建四个类分别实现四个接口即可
    • 还需要配置一个文件 resources/META-INF/spring.factories
    • # Initializers
      org.springframework.context.ApplicationContextInitializer=\
      com.atguigu.springboot07.listener.HelloApplicationContextInitializer
      
      org.springframework.boot.SpringApplicationRunListener=\
      com.atguigu.springboot07.listener.HelloSpringApplicationRunListener
    • public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
          @Override
          public void initialize(ConfigurableApplicationContext applicationContext) {
              System.out.println("ApplicationContextInitializer...initialize"+applicationContext);
          }
      }
      
      public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
      
          /**
           * 如果没有这个有参构造,那么会报错,
           *  Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.atguigu.springboot07.listener.HelloSpringApplicationRunListener
           * 意思就是: 不能实例化这个类SpringApplicationRunListener,进入这个类发现这个类没有无参构造,只有有参构造
           */
          public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
      
          }
      
          @Override
          public void starting() {
              System.out.println("SpringApplicationRunListener...starting...");
          }
      
          @Override
          public void environmentPrepared(ConfigurableEnvironment environment) {
              Object o = environment.getSystemProperties().get("os.name");
              System.out.println("SpringApplicationRunListener...environmentPrepared..."+o);
          }
      
          @Override
          public void contextPrepared(ConfigurableApplicationContext context) {
              System.out.println("SpringApplicationRunListener...contextPrepared...");
          }
      
          @Override
          public void contextLoaded(ConfigurableApplicationContext context) {
              System.out.println("SpringApplicationRunListener...contextLoaded...");
          }
      
          @Override
          public void failed(ConfigurableApplicationContext context, Throwable exception) {
              System.out.println("SpringApplicationRunListener...failed...");
          }
      }
      
    • HelloApplicationRunner 和 HelloCommandLineRunner 两个类都需要添加一个注解 @Component
    • @Component
      public class HelloApplicationRunner implements ApplicationRunner {
          @Override
          public void run(ApplicationArguments args) throws Exception {
              System.out.println("ApplicationRunner...run...");
          }
      }
      
      @Component
      public class HelloCommandLineRunner implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
          }
      }
    • 这里有个问题:
    • public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
      
          /**
           * 如果没有这个有参构造,那么会报错,
           *  Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.atguigu.springboot07.listener.HelloSpringApplicationRunListener
           * 意思就是: 不能实例化这个类SpringApplicationRunListener,进入这个类发现这个类没有无参构造,只有有参构造
           */
          public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
      
          }

启动流程:

  • Spring Boot 的入口类
  • @SpringBootApplication
    public class SpringBootBestPracticeApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringBoot07Application.class, args);
        }
    }
  • 做过 Spring Boot 项目的都知道,上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。

  • 来看一下这个类的run方法调用关系源码:

  • /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings.
     * @param primarySource the primary source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
    
    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param primarySources the primary sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
  • 第一个参数primarySource:加载的主要资源类

  • 第二个参数 args:传递给应用的应用参数

  • 先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来分析这个启动源码

  • SpringApplication 的实例化过程:

    • 接着上面的 SpringApplication 构造方法进入以下源码:

    • /**
       * Create a new {@link SpringApplication} instance. The application context will load
       * beans from the specified primary sources (see {@link SpringApplication class-level}
       * documentation for details. The instance can be customized before calling
       * {@link #run(String...)}.
       * @param primarySources the primary bean sources
       * @see #run(Class, String[])
       * @see #SpringApplication(ResourceLoader, Class...)
       * @see #setSources(Set)
       */
      public SpringApplication(Class<?>... primarySources) {
          this(null, primarySources);
      }
      
      
      /**
       * Create a new {@link SpringApplication} instance. The application context will load
       * beans from the specified primary sources (see {@link SpringApplication class-level}
       * documentation for details. The instance can be customized before calling
       * {@link #run(String...)}.
       * @param resourceLoader the resource loader to use
       * @param primarySources the primary bean sources
       * @see #run(Class, String[])
       * @see #setSources(Set)
       */
      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
          // 1、资源初始化资源加载器为 null
          this.resourceLoader = resourceLoader;
          // 2、断言主要加载资源类不能为 null,否则报错
          Assert.notNull(primarySources, "PrimarySources must not be null");
          // 3、初始化主要加载资源类集合并去重
          this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
          // 4、推断当前 WEB 应用类型(REACTIVE、NONE、SERVLET)
          /**
           * NONE: 非 WEB 项目
           * SERVLET: servlet WEB 项目
           * REACTIVE: 响应式 WEB 项目
           */
          this.webApplicationType = WebApplicationType.deduceFromClasspath();
          // 5、设置初始化器,得到Spring工厂实例
          setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      
          // 6、设置监听器
          setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
          // 7、推断主入口应用类
          this.mainApplicationClass = deduceMainApplicationClass();
      
      }
  • 调用这个对象的 run 方法:

    • 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续来看下这个 SpringApplication 对象的 run 方法的源码和运行流程。

    • /**
       * Run the Spring application, creating and refreshing a new
       * {@link ApplicationContext}.
       * @param args the application arguments (usually passed from a Java main method)
       * @return a running {@link ApplicationContext}
       */
      public ConfigurableApplicationContext run(String... args) {
          // 1、创建并启动计时监控类
          StopWatch stopWatch = new StopWatch();
          stopWatch.start();
          // 2、初始化应用上下文和异常报告集合
          ConfigurableApplicationContext context = null;
          Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
          // 3、设置系统属性 `java.awt.headless` 的值,默认值为:true
          configureHeadlessProperty();
          // 4、创建所有 Spring 运行监听器并发布应用启动事件
          SpringApplicationRunListeners listeners = getRunListeners(args);
          listeners.starting();
          try {
              // 5、初始化默认应用参数类
              ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
              // 6、根据运行监听器和应用参数来准备 Spring 环境
              ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
              configureIgnoreBeanInfo(environment);
              // 7、创建 Banner 打印类
              Banner printedBanner = printBanner(environment);
              // 8、创建应用上下文
              context = createApplicationContext();
              // 9、准备异常报告器
              exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      			new Class[] { ConfigurableApplicationContext.class }, context);
              // 10、准备应用上下文
              prepareContext(context, environment, listeners, applicationArguments,printedBanner);
              // 11、刷新应用上下文
              refreshContext(context);
              // 12、应用上下文刷新后置处理
              afterRefresh(context, applicationArguments);
              // 13、停止计时监控类
              stopWatch.stop();
              // 14、输出日志记录执行主类名、时间信息
              if (this.logStartupInfo) {
                  new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
              }
              // 15、发布应用上下文启动完成事件
              listeners.started(context);
              // 16、执行所有 Runner 运行器
              callRunners(context, applicationArguments);
          }
          catch (Throwable ex) {
              handleRunFailure(context, ex, exceptionReporters, listeners);
              throw new IllegalStateException(ex);
          }
          try {
              // 17、发布应用上下文就绪事件
              listeners.running(context);
          }
          catch (Throwable ex) {
              handleRunFailure(context, ex, exceptionReporters, null);
              throw new IllegalStateException(ex);
          }
          // 18、返回应用上下文
          return context;
      }
    • 这里只是描述的不详细没有深入方法进行说明,我找了一篇博客详细描述了整个方法调用

    • https://www.jianshu.com/p/414d3e2f04e9

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值