美文网首页
Spring Boot的启动过程

Spring Boot的启动过程

作者: buzzerrookie | 来源:发表于2019-03-23 18:35 被阅读0次

    本文从SpringApplication类开始分析Spring Boot应用的启动过程,使用的Spring Boot版本是1.5.15.RELEASE。

    成员变量

    public class SpringApplication {
        /**
         * The class name of application context that will be used by default for non-web
         * environments.
         */
        public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
                + "annotation.AnnotationConfigApplicationContext";
    
        /**
         * The class name of application context that will be used by default for web
         * environments.
         */
        public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
                + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
    
        private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
                "org.springframework.web.context.ConfigurableWebApplicationContext" };
    
        /**
         * Default banner location.
         */
        public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;
    
        /**
         * Banner location property key.
         */
        public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;
    
        private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
    
        private static final Log logger = LogFactory.getLog(SpringApplication.class);
    
        private final Set<Object> sources = new LinkedHashSet<Object>();
    
        private Class<?> mainApplicationClass;
    
        private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
    
        private boolean logStartupInfo = true;
    
        private boolean addCommandLineProperties = true;
    
        private Banner banner;
    
        private ResourceLoader resourceLoader;
    
        private BeanNameGenerator beanNameGenerator;
    
        private ConfigurableEnvironment environment;
    
        private Class<? extends ConfigurableApplicationContext> applicationContextClass;
    
        private boolean webEnvironment;
    
        private boolean headless = true;
    
        private boolean registerShutdownHook = true;
    
        private List<ApplicationContextInitializer<?>> initializers;
    
        private List<ApplicationListener<?>> listeners;
    
        private Map<String, Object> defaultProperties;
    
        private Set<String> additionalProfiles = new HashSet<String>();
    }
    
    • DEFAULT_CONTEXT_CLASS常量表示非Web环境默认的应用上下文是AnnotationConfigApplicationContext类型;
    • DEFAULT_WEB_CONTEXT_CLASS常量表示Web环境下默认的应用上下文是AnnotationConfigEmbeddedWebApplicationContext类型;
    • sources表示bean来源;
    • mainApplicationClass表示启动类类型;
    • resourceLoader表示资源加载的策略;
    • beanNameGenerator表示生成bean名称的策略;
    • environment表示使用的环境;
    • applicationContextClass表示创建的应用上下文的类型;
    • webEnvironment表示当前是否是Web环境;
    • initializers表示添加的ApplicationContextInitializer;
    • listeners表示添加的ApplicationListener;
    • ...

    构造函数

    SpringApplication类的两个构造函数如下,它们都调用initialize方法做初始化工作。

    public SpringApplication(Object... sources) {
        initialize(sources);
    }
    
    public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
        this.resourceLoader = resourceLoader;
        initialize(sources);
    }
    

    initialize方法代码如下:

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    

    该方法做了如下工作:

    1. deduceWebEnvironment方法检测当前是否是Web环境,只有当Servlet接口和ConfigurableWebApplicationContext接口都存在且能被加载时才是Web环境;
      private boolean deduceWebEnvironment() {
          for (String className : WEB_ENVIRONMENT_CLASSES) {
              if (!ClassUtils.isPresent(className, null)) {
                  return false;
              }
          }
          return true;
      }
      
    2. getSpringFactoriesInstances方法从jar包的META/spring.factories文件中获取特定类型的工厂实现类限定名并实例化它们,此处是创建了ApplicationContextInitializerApplicationListener两种类型的工厂实现类,并分别保存到initializers和listeners成员变量中。每个jar包都可能有spring.factories文件,以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories为例,该文件包含4种ApplicationContextInitializer和9种ApplicationListener;
      # Application Context Initializers
      org.springframework.context.ApplicationContextInitializer=\
      org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
      org.springframework.boot.context.ContextIdApplicationContextInitializer,\
      org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
      org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
      
      # Application Listeners
      org.springframework.context.ApplicationListener=\
      org.springframework.boot.ClearCachesApplicationListener,\
      org.springframework.boot.builder.ParentContextCloserApplicationListener,\
      org.springframework.boot.context.FileEncodingApplicationListener,\
      org.springframework.boot.context.config.AnsiOutputApplicationListener,\
      org.springframework.boot.context.config.ConfigFileApplicationListener,\
      org.springframework.boot.context.config.DelegatingApplicationListener,\
      org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
      org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
      org.springframework.boot.logging.LoggingApplicationListener
      
    3. deduceMainApplicationClass方法找出main方法所在的类并保存到mainApplicationClass成员变量。
      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静态方法代码如下所示,可见其调用了上文的构造函数和run成员方法。

    public static ConfigurableApplicationContext run(Object source, String... args) {
        return run(new Object[] { source }, args);
    }
    
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }
    

    run成员方法代码如下:

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
    

    run成员方法的主要流程如下:

    1. 创建SpringApplicationRunListener类型的实例;
    2. 为应用上下文准备环境;
    3. 创建应用上下文;
    4. 准备应用上下文;
    5. 刷新应用上下文。

    创建SpringApplicationRunListener

    在创建SpringApplicationRunListener的过程中,首先getRunListeners方法在各META/spring.factories文件查找org.springframework.boot.SpringApplicationRunListener的工厂实现类,然后以该SpringApplication对象和main方法的命令行参数为实参调用这些实现类的构造函数实例化它们,最后各SpringApplicationRunListener的starting回调方法被调用。

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }
    
    • SpringApplicationRunListener接口专门用于监听run方法,其代码如下所示,该接口的实现类需要声明一个公有的以SpringApplication和字符串数组为参数的构造函数。
      public interface SpringApplicationRunListener {
      
          void starting();
      
          void environmentPrepared(ConfigurableEnvironment environment);
      
          void contextPrepared(ConfigurableApplicationContext context);
      
          void contextLoaded(ConfigurableApplicationContext context);
      
          void finished(ConfigurableApplicationContext context, Throwable exception);
      }
      
    • SpringApplicationRunListeners类是SpringApplicationRunListener的集合,其方法内部都是依次调用各SpringApplicationRunListener同名的回调方法。
      class SpringApplicationRunListeners {
          private final Log log;
          private final List<SpringApplicationRunListener> listeners;
      
          SpringApplicationRunListeners(Log log,
                  Collection<? extends SpringApplicationRunListener> listeners) {
              this.log = log;
              this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
          }
      
          public void starting() {
              for (SpringApplicationRunListener listener : this.listeners) {
                  listener.starting();
              }
          }
      
          public void environmentPrepared(ConfigurableEnvironment environment) {
              for (SpringApplicationRunListener listener : this.listeners) {
                  listener.environmentPrepared(environment);
              }
          }
      
          // 省略一些代码
      }
      
    • 以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories为例,该文件只包含一个EventPublishingRunListener;
      # Run Listeners
      org.springframework.boot.SpringApplicationRunListener=\
      org.springframework.boot.context.event.EventPublishingRunListener
      
      EventPublishingRunListener类的部分代码如下,它的构造函数将SpringApplication的listeners成员变量保存的各ApplicationListener加入事件广播器中,其他回调方法在内部广播了相应的事件给各ApplicationListener。
      public EventPublishingRunListener(SpringApplication application, String[] args) {
          this.application = application;
          this.args = args;
          this.initialMulticaster = new SimpleApplicationEventMulticaster();
          for (ApplicationListener<?> listener : application.getListeners()) {
              this.initialMulticaster.addApplicationListener(listener);
          }
      }
      
      @Override
      @SuppressWarnings("deprecation")
      public void starting() {
          this.initialMulticaster
                  .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
      }
      
      @Override
      public void environmentPrepared(ConfigurableEnvironment environment) {
          this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                  this.application, this.args, environment));
      }
      
      @Override
      public void contextPrepared(ConfigurableApplicationContext context) {
      
      }
      
      // 省略一些代码
      

    准备环境

    准备环境是由prepareEnvironment方法完成的,其代码如下所示。

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }
    
    • 首先创建环境,若是Web环境则创建StandardServletEnvironment,否则创建StandardEnvironment;
      private ConfigurableEnvironment getOrCreateEnvironment() {
          if (this.environment != null) {
              return this.environment;
          }
          if (this.webEnvironment) {
              return new StandardServletEnvironment();
          }
          return new StandardEnvironment();
      }
      
    • 然后是配置环境,分别配置了属性源和配置文件;
      protected void configureEnvironment(ConfigurableEnvironment environment,
              String[] args) {
          configurePropertySources(environment, args);
          configureProfiles(environment, args);
      }
      
      protected void configurePropertySources(ConfigurableEnvironment environment,
              String[] args) {
          MutablePropertySources sources = environment.getPropertySources();
          if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
              sources.addLast(
                      new MapPropertySource("defaultProperties", this.defaultProperties));
          }
          if (this.addCommandLineProperties && args.length > 0) {
              String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
              if (sources.contains(name)) {
                  PropertySource<?> source = sources.get(name);
                  CompositePropertySource composite = new CompositePropertySource(name);
                  composite.addPropertySource(new SimpleCommandLinePropertySource(
                          name + "-" + args.hashCode(), args));
                  composite.addPropertySource(source);
                  sources.replace(name, composite);
              }
              else {
                  sources.addFirst(new SimpleCommandLinePropertySource(args));
              }
          }
      }
      
      protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
          environment.getActiveProfiles(); // ensure they are initialized
          // But these ones should go first (last wins in a property key clash)
          Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
          profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
          environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
      }
      
    • 最后各SpringApplicationRunListener的environmentPrepared回调方法被调用。

    创建应用上下文

    createApplicationContext方法创建上下文,如果applicationContextClass字段指定了上下文类型则创建该类型的应用上下文,否则对Web环境默认创建AnnotationConfigEmbeddedWebApplicationContext,对非Web环境创建AnnotationConfigApplicationContext。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }
    

    所创建的应用上下文即是DispatcherServlet的应用上下文,这与传统的Spring MVC工程不同。

    准备应用上下文

    prepareContext方法用于准备上面创建的应用上下文,其代码如下所示。

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
    
        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
    
        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }
    

    该方法主要做了以下几件事:

    • 为创建的应用上下文设置了之前创建的环境;
    • postProcessApplicationContext方法做了创建应用上下文后的处理工作,为应用上下文注册了bean名称生成策略,子类可以重写该方法自定义其他工作;
    • applyInitializers方法按各ApplicationContextInitializer的添加顺序调用其initialize回调方法,请注意除了上文提到jar包的META/spring.factories文件中可以指定该接口的工厂实现类之外,还可以在调用run方法之前通过SpringApplication类的addInitializers成员方法添加ApplicationContextInitializer;
    • 各SpringApplicationRunListener的contextPrepared回调方法被调用;
    • load方法将bean定义加载到应用上下文中(还未实例化);
    • 各SpringApplicationRunListener的contextLoaded回调方法被调用。

    刷新应用上下文

    refreshContext方法刷新创建的应用上下文,根据上一步加载的bean定义实例化各单例bean。

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
    
    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }
    

    应用上下文刷新后

    应用上下文刷新后run方法调用了afterRefresh方法执行所有的Runner,并调用各SpringApplicationRunListener的finished回调方法。

    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
    

    afterRefresh方法代码如下所示:

    protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
        callRunners(context, args);
    }
    
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }
    
    • 首先找出应用上下文中所有的ApplicationRunnerCommandLineRunner,实际使用时这两个接口的实现类用@Component注解修饰即可;
    • 然后对这些ApplicationRunner和CommandLineRunner按升序排序,它们的实现类都可以选择地实现Ordered接口或者用@Order注解修饰;
    • 最后按顺序调用这些Runner的run回调方法。

    相关文章

      网友评论

          本文标题:Spring Boot的启动过程

          本文链接:https://www.haomeiwen.com/subject/nhtsvqtx.html