searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

SpringBoot 框架启动类 SpringApplication 剖析

2024-06-19 09:36:44
8
0

SpringBoot框架目前广泛应用在各大企业的各种工业级项目中,如Java作为天翼云TeleDB后台团队主语言,SpringBoot框架在各大项目中广泛应用,并且积累了沉淀了很多SpringBoot框架的使用经验和对SpringBoot框架进行了很多源码级的研究。这里主要对SpringBoot框架启动类SpringApplication进行源码剖析,方便开发者对SpringBoot框架的运作机制有更深的理解,提高解决工作中遇到相关问题的效率。

1.1 应用启动:SpringApplicaiton 的 run 方法剖析

基于 SpringBoot 框架搭建的项目是可以通过执行项目启动类 Application 的 main 方法并以独立进程的方式来启动运行,其中由 SpringBoot 官网提供的项目压缩包的项目启动类 Application 的典型实现如下:其中 Demo 是对应项目的名字。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

   public static void main(String[] args) {
      // 完成SpringBoot框架和应用的启动运行
      SpringApplication.run(DemoApplication.class, args);
   }

}

​ 由 Application 类的定义可知,该类使用了 @SpringBootApplication 注解修饰,在 main 方法中只有一个方法调用,即调用了 SpringApplication 类的静态方法 run,在 run 方法内部完成应用的启动运行,所以了解该 run 方法的内部实现是深入学习 SpringBoot 框架的核心设计的必经之路。

​ SpringApplication 类的静态方法 run 的定义如下:使用当前的项目启动类 Application 和类的启动参数 args 作为方法入参,在内部会最终创建一个 SpringApplication 类的对象并调用该对象的成员方法 run 方法完成 SpringBoot 框架,或者称为应用的启动。

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

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  // 创建一个SpringApplication对象并调用该对象的成员方法run
   return new SpringApplication(primarySources).run(args);
}

SpringBoot 框架在内部实现当中也是依赖 Spring 框架的 IOC 容器来完成 Java 对象的相关管理的,如 Java 对象的创建和对象间的依赖注入。所以在 SpringApplication 对象的成员方法 run 中也是通过 Spring 框架的 Application 接口实现类来完成 IOC 容器的创建,具体实现如下:

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);
      // 将命令行参数加载到环境变量environment
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);

      // 1. 创建应用上下文,即Spring IOC容器
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      // 2. 初始化Spring IOC容器前的准备工作
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 3. 初始化 Spring IOC 容器
      refreshContext(context);
      // 4. 初始化Spring IOC容器后的相关工作
      afterRefresh(context, applicationArguments);

      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }

      // 监听器启动完成
      listeners.started(context);

      // Context中获取CommandRunner并调用SpringBootCommandRunner相关类
      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);
   }
   
   // 返回Spring IOC容器对应的Application接口实现类对象
   return context;
}

在 run 方法内部主要完成 Spring IOC 容器的创建与初始化,各个步骤的作用请看以上代码注释,而关于 Spring IOC 容器创建与初始化的核心方法调用顺序为:

  1. createApplicationContext 方法调用:创建Spring IOC容器,即创建 Application 接口的实现类对象,createApplicationContext 方法的核心实现如下:基于 Servlet 实现的 Java web 项目使用的是 SpringBoot 框架对于 Application 接口的实现类 AnnotationConfigServletWebServerApplicationContext,在该实现类的 refresh 方法中会完成 Servlet 引擎的加载和启动,从而实现在指定监听端口监听客户端请求,即与 Tomcat 或 Jetty 相同的功能。

    protected ConfigurableApplicationContext createApplicationContext() {
       // 根据应用类型确定需要使用哪个Application接口的实现类
       Class<?> contextClass = this.applicationContextClass;
       if (contextClass == null) {
          try {
             switch (this.webApplicationType) {
             // 基于servlet实现的Java Web项目,使用Application接口实现类为:
             // AnnotationConfigServletWebServerApplicationContext
             case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
             case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
             default:
                // 默认为基于Java类配置的AnnotationConfigApplicationContext
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
             }
          }
          catch (ClassNotFoundException ex) {
             throw new IllegalStateException(
                   "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
          }
       }
       // 创建Application接口的实现类对象实例
       return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
  2. prepareContext 方法调用:完成初始化Spring IOC容器前的准备工作。步骤 1 只是创建了 Application 接口的实现类对象 context,此时并没有真正启动 Spring IOC 容器,故在该步骤会继续为下一步的 Spring IOC 容器的启动与初始化进行一些准备工作。该步骤的一个核心功能是将项目启动类 Application 加载到Spring IOC容器中,即生成 Application 类对应的 BeanDefinition 对象,具体在 prepareContext 方法内部的 load 方法完成:

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
          SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner)
     {
       // 将环境属性集合注入到context
       context.setEnvironment(environment);
       // 后置处理context
       postProcessApplicationContext(context);
       
       // 省略其他代码
      
       // Application类是sources集合中的一个元素
       Set<Object> sources = getAllSources();
       Assert.notEmpty(sources, "Sources must not be empty");
       // 将项目启动类Application加载到Spring IOC容器中
       load(context, sources.toArray(new Object[0]));
       listeners.contextLoaded(context);
    }
  3. refreshContext 方法调用:该方法是实际完成 Spring IOC 容器的初始化,即 Java 对象的加载与创建,Java 对象间的依赖注入等,具体为调用 Spring 框架中定义 IOC 容器初始化流程的 AbstractApplication 抽象类的 refresh 方法来完成,这个方法的具体实现在上一小节已经介绍过,如有疑问可以回去看看。

    private void refreshContext(ConfigurableApplicationContext context) {
       // 调用AbstractApplicaitonrefresh方法来完成Spring IOC容器的初始化
       refresh(context);
       if (this.registerShutdownHook) {
          try {
             // 注册关闭hook
             context.registerShutdownHook();
          }
          catch (AccessControlException ex) {
             // Not allowed in some environments.
          }
       }
    }

    以上 refresh 方法的实现:

    protected void refresh(ApplicationContext applicationContext) {
       Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
       // 调用AbstractApplicationContextrefresh方法
       ((AbstractApplicationContext) applicationContext).refresh();
    }
  4. afterRefresh 方法调用:完成 Spring IOC 容器初始化后的一些处理功能,该方法目前是一个 protected 修饰的空方法,主要用于拓展性方面考虑。

​ 通过以上代码分析可知,SpringBoot 框架在内部主要是通过拓展实现 Spring 框架的 Application 接口来自定义 SpringBoot 框架的 IOC 容器的初始化过程,实现 SpringBoot 框架自身的功能,如基于 Java 进程独立启动运行等。

1.2 应用总配置类:Application 类与 @SpringApplicationContext 注解

​ 细心的读者可能会留意到项目启动类 Application ,如以上介绍的 DemoApplication 会使用 @SpringApplicationContext 注解,如下:

@SpringBootApplication
public class DemoApplication {

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

}

​ 为什么需要使用 @SpringApplicationContext 注解,这个注解的作用是什么?其实这个注解是 SpringBoot 框架提供的一个核心注解,该注解是 SpringBoot 框架实现 Java类配置替代 XML 文件配置、SpringBoot 的自动配置特性的入口,该注解的定义如下:有定义可知,该注解是一个复合注解,由多个注解组成,核心包括:@SpringBootConfiguration 注解,@EnableAutoConfiguration 注解和 @ComponentScan 注解。

@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 {
 // 省略其他代码
}

​ SpringBoot 框架推崇的是不使用 XML 文件来作为基于 Spring 框架搭建的项目的应用配置,而是使用基于 Java 类配置的方式,所以由 @SpringBootApplication 注解的定义可知,该注解由多个注解组成,其中一个注解是 @Configuration,所以项目启动类 Application 其实就是一个 Java 配置类来的,我们可以在该类中对当前的项目进行配置,如定义某个 Java 对象,或者称为 bean 对象,则可以结合 @Bean 注解来实现。关于 @Configuration 的更多知识在后面章节详细分析。

​ 除此之外,单单基于 @Configuration 注解实现基于 Java 类的应用配置是不够的,SpringBoot 框架需要使用 @ComponentScan 注解来完成相关 Java 对象的自动扫描和注册,如使用了 @Controller 注解,@Service 注解或 @Repository 注解的类的对象的加载。

​ 最后 @EnableAutoConfiguration 注解是 SpringBoot 实现自动配置的核心注解,该注解主要用于启动 SpringBoot 框架的自动配置特性,具体的自动配置生效需要配合 SpringBoot 的 starter 包和 @Configuration 注解来实现。

​ @Configuration 注解,@ComponentScan 注解是 Spring 框架的两个核心注解,以及 @EnableAutoConfiguration 是 SpringBoot 框架的自动配置实现的核心注解,关于这些注解的更多知识,包括用户和实现原理在后面章节再详细分析,使得读者朋友能够更好地理解 Spring 框架和 SpringBoot 框架的使用,达到知其然知其所以然。

1.3 总结

​ SpringBoot 框架定义了 SpringApplication 类来作为自身的一个框架入口类,即基于 SpringBoot 框架搭建的项目,在代码层面都需要通过 SpringApplication 类来使用 SpringBoot 框架,具体为在项目启动类 Application 的静态 main 方法中调用 SpringApplication 类的静态方法 run。

​ SpringApplication 类的静态方法 run 在内部会为当前项目创建一个对应的 SpringApplication 类对象,最终调用该对象的成员方法 run 来完成 Spring 的 IOC 容器的创建与初始化,以及完成 servlet 引擎的加载,从而可以实现以独立 Java 进程启动的方式来提供服务。

​ 除此之外,SpringBoot 框架推崇的是基于 Java 类的方式来对项目进行配置,从而实现基于 SpringBoot 搭建的项目不再需要如传统的 Spring 项目一样,使用繁琐的 XML 配置文件来管理项目配置。而 SpringBoot 框架对于这个功能的实现方式主要是通过定义一个 @SpringBootApplication 注解来作为实现入口,然后 SpringBoot 框架会基于该注解来实现基于 Java 类配置的功能和 SpringBoot 的自动配置功能。

0条评论
0 / 1000
javaxyz
3文章数
0粉丝数
javaxyz
3 文章 | 0 粉丝