购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 run方法分析

本节将对run方法进行分析,该方法是Spring Boot项目启动的核心方法,具体处理代码如下:

在上述方法中主要的处理流程如下:

(1)创建秒表对象并将秒表开启;

(2)创建默认的引导上下文,具体处理方法是createBootstrapContext;

(3)设置Java的属性值,具体设置的是java.awt.headless属性;

(4)获取SpringApplicationRunListener集合;

(5)启动第(4)步中获取的监听器;

(6)将参数args转换成ApplicationArguments;

(7)获取环境对象,具体处理方法是prepareEnvironment;

(8)配置是否需要忽略的Bean,具体处理方法是configureIgnoreBeanInfo;

(9)获取banner并将banner输出;

(10)创建应用上下文,具体处理方法是createApplicationContext;

(11)设置应用上下文的应用阶段;

(12)准备上下文;

(13)刷新上下文;

(14)进行刷新上下文后的补充操作,具体处理方法是afterRefresh,注意该方法目前是空方法;

(15)将秒表停止;

(16)进行启动日志输出;

(17)监听器开始执行;

(18)执行ApplicationRunner或者CommandLineRunner相关接口的实现类,具体处理方法是callRunners。

在上述处理流程中如果出现异常会进行handleRunFailure方法的调度,handleRunFailure代码详情如下:

在handleRunFailure方法中主要的处理流程如下:

(1)异常code处理,将异常code解析后组成Spring的事件并且推送,具体事件类型是ExitCodeEvent;

(2)如果监听器列表不为空会进行监听器的failed方法调度。

回到run方法,在run方法中需要着重关注的方法有如下7个,本节后续将会对它们进行分析。

(1)createBootstrapContext方法;

(2)prepareEnvironment方法;

(3)configureIgnoreBeanInfo方法;

(4)printBanner方法;

(5)prepareContext方法;

(6)refreshContext方法;

(7)callRunners方法。

2.3.1 createBootstrapContext方法分析

本节将对createBootstrapContext方法进行分析,具体处理代码如下:

private DefaultBootstrapContext createBootstrapContext() {
   // 创建默认的引导上下文
   DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
   // 循环调度BootstrapRegistryInitializer的initialize方法
   this.bootstrapRegistryInitializers.forEach((initializer) ->
 initializer.initialize(bootstrapContext));
   // 返回默认的引导上下文
   return bootstrapContext;
}

createBootstrapContext方法的主要目的是创建默认的引导上下文,具体执行流程如下:

(1)创建默认的引导上下文;

(2)通过成员变量bootstrapRegistryInitializers初始化第(1)步创建的引导上下文。

2.3.2 prepareEnvironment方法分析

本节将对prepareEnvironment方法进行分析,该方法的主要目标是进行环境对象的准备,具体处理代码如下:

在prepareEnvironment方法中主要的处理流程如下:

(1)获取环境对象,关于环境对象的获取提供了两种方式,第一种是通过成员变量获取,第二种是根据应用类型进行创建,创建StandardServletEnvironment、StandardReactiveWebEnvironment和StandardEnvironment的具体处理方法是getOrCreateEnvironment;

(2)配置环境信息,具体处理方法是configureEnvironment;

(3)追加或者移除部分属性,具体处理方法是ConfigurationPropertySources.attach;

(4)触发环境准备事件,具体处理交给SpringApplicationRunListeners进行负责;

(5)将环境对象中的属性表进行重排序。排序规则:从属性表中移除defaultProperties名称的属性,如果该属性不为空则放入最后一个索引位上;

(6)绑定环境,具体处理方法是bindToSpringApplication;

(7)判断是否是自定义环境对象,如果是自定义则会进行配置对象转换,并且在转换后再次进行第(3)步的操作流程,操作完成后返回。

1.getOrCreateEnvironment方法分析

这里将对getOrCreateEnvironment方法进行分析,该方法用于获取环境对象,具体处理代码如下:

private ConfigurableEnvironment getOrCreateEnvironment() {
   // 成员变量获取
   if (this.environment != null) {
      return this.environment;
   }
   // 通过Web应用类型获取
   switch (this.webApplicationType) {
      case SERVLET:
          return new StandardServletEnvironment();
      case REACTIVE:
          return new StandardReactiveWebEnvironment();
      default:
          return new StandardEnvironment();
   }
}

在getOrCreateEnvironment方法中主要关注不同的Web应用和环境对象的关系,详细信息见表2-1。

表2-1 Web应用与环境对象的关系

在上述三个Web应用类型中比较常用的是SERVLET。

2.configureEnvironment方法分析

这里将对configureEnvironment方法进行分析,该方法用于配置环境信息,具体处理代码如下:

    protected void configureEnvironment(ConfigurableEnvironment environment, String[]
args) {
        // 是否添加转换服务
        if (this.addConversionService) {
           // 获取应用级别的转换服务
           ConversionService conversionService =
    ApplicationConversionService.getSharedInstance();
           // 为环境对象添加转换服务
              environment.setConversionService((ConfigurableConversionService)
conversionService);
        }
        // 配置属性源
        configurePropertySources(environment, args);
        // 配置profiles
        configureProfiles(environment, args);
    }

在configureEnvironment方法中主要的处理流程如下:

(1)判断是否需要添加转换服务,如果需要,则会获取ApplicationConversionService对象作为转换服务将其加入环境对象中;

(2)配置属性源,具体处理方法是configurePropertySources;

(3)配置profiles相关内容,具体处理方法是configureProfiles,目前该方法为空方法未作处理。

接下来将对configurePropertySources方法进行分析,具体处理代码如下:

在configurePropertySources方法中具体的处理流程如下:

(1)从环境对象中获取属性表;

(2)判断成员变量defaultProperties是否为空,如果不为空会和第(1)步中得到的数据进行合并;

(3)判断是否需要将命令行参数作为属性加入属性表中(默认需要),判断命令行参数数量是否大于0,如果同时满足这两条会将命令行参数加入第(1)步获取的数据中。

在上述流程中第(3)步的细节如下:

(1)获取命令行参数的键数据,这是一个静态变量,具体变量是CommandLine-PropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME。

(2)判断环境变量中的属性源对象是否包含键数据,如果不包含会直接进行头插法加入数据,如果包含会进行历史数据和新数据的合并操作,在合并后将环境对象中的属性源进行替换。

3.ConfigurationPropertySources.attach方法分析

这里将对ConfigurationPropertySources.attach方法进行分析,具体处理代码如下:

在上述代码中主要的处理流程如下:

(1)从环境对象中获取属性表;

(2)获取configurationProperties属性,获取后变量名为attached;

(3)如果变量attached不为空并且attached中的源对象不等于第(1)步中获取的数据,则会将configurationProperties名称的属性移除并且将attached置为null;

(4)如果attached变量为空,则将进行头插法加入属性源数据信息。

4.EnvironmentConverter分析

在前文关于环境对象准备时需要使用一个叫作环境转换的对象,它是Environment-Converter,这里将对该对象进行分析,有关EnvironmentConverter成员变量详细内容见表2-2。

表2-2 EnvironmentConverter成员变量

在该对象中主要关注的方法入口是convertEnvironmentIfNecessary,具体处理代码如下:

    StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment
environment, Class<? extends StandardEnvironment> type) {
        if (type.equals(environment.getClass())) {
           return (StandardEnvironment) environment;
        }
        return convertEnvironment(environment, type);
    }

在convertEnvironmentIfNecessary方法中主要的处理流程如下:

(1)如果传入的两个参数类型相同会直接进行一次强制转换并返回。

(2)如果传入的参数类型不同会进行进一步转换,具体处理方法是convertEnvironment。

在上述处理流程中最关键的是convertEnvironment方法,详细处理代码如下:

    private StandardEnvironment convertEnvironment(ConfigurableEnvironment
environment, Class<? extends StandardEnvironment> type) {
        // 通过type创建StandardEnvironment类型的对象
        StandardEnvironment result = createEnvironment(type);
        // 设置激活的profile
        result.setActiveProfiles(environment.getActiveProfiles());
        // 设置转换服务
        result.setConversionService(environment.getConversionService());
        // 属性复制
        copyPropertySources(environment, result);
        // 返回结果
        return result;
    }

在上述代码中主要的处理流程如下:

(1)通过type创建StandardEnvironment类型的对象,具体创建形式是反射创建;

(2)设置激活的profile;

(3)设置转换服务;

(4)将环境配置的数据复制给第(1)步中创建的对象。

(5)返回第(1)步中的创建结果。

2.3.3 configureIgnoreBeanInfo方法分析

本节将对configureIgnoreBeanInfo方法进行分析,具体处理代码如下:

    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        // 获取系统变量 spring.beaninfo.ignore,如果无法获取则进一步处理
         if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_
NAME) == null) {
           // 在环境变量中获取spring.beaninfo.ignore对应的数据
           Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.
class, Boolean.TRUE);
           // 设置spring.beaninfo.ignore属性
    System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
        }
    }

configureIgnoreBeanInfo方法主要的目标是设置spring.beaninfo.ignore属性到系统变量中,数据来源是环境变量的spring.beaninfo.ignore属性。

2.3.4 printBanner方法分析

本节将对printBanner方法进行分析,具体处理代码如下:

在上述代码中主要的处理流程如下:

(1)判断是否开启banner,如果未开启将返回null;

(2)获取资源加载接口;

(3)通过资源加载器和成员变量banner创建SpringApplicationBannerPrinter;

(4)判断是否需要输出到日志文件,如果需要则直接进行日志写出,如果不需要将通过System.out写出到控制台。

2.3.5 prepareContext方法分析

本节将对prepareContext方法进行分析,该方法用于准备应用上下文,具体处理代码如下:

在prepareContext方法中主要的处理流程如下:

(1)为应用上下文设置环境对象;

(2)进行应用上下文的后置处理,具体处理方法是postProcessApplicationContext;

(3)应用初始化相关内容,主要配合ApplicationContextInitializer进行处理,具体处理方法是applyInitializers;

(4)通过SpringApplicationRunListeners触发上下文准备事件;

(5)关闭引导上下文对象;

(6)从上下文中获取Bean工厂;

(7)进行单例注册,注册内容是springApplicationArguments(Spring应用参数对象);

(8)在banner不为空的情况下进行单例注册,注册内容是springBootBanner;

(9)判断第(6)步中获取的Bean工厂是否是DefaultListableBeanFactory类型,如果是将会设置允许覆盖Bean定义的标记,设置数据源是成员变量allowBeanDefinitionOverriding;

(10)判断是否延迟加载,如果是将会向上下文添加Bean工厂后置处理器,添加的类型是LazyInitializationBeanFactoryPostProcessor;

(11)获取所有源,源是指成员变量中的primarySources和sources;

(12)将第(11)步中得到的源进行Bean实例加载;

(13)通过SpringApplicationRunListeners触发上下文加载实例。

在上述13步操作流程中还涉及一些方法的调度,下面将对postProcessApplicationContext、applyInitializers和load三个方法进行分析。

1.postProcessApplicationContext方法分析

下面将对postProcessApplicationContext方法进行分析,该方法可以通过子类来进行更新,这里主要讨论的是SpringApplication中的postProcessApplicationContext方法,具体处理代码如下:

在postProcessApplicationContext方法中主要的处理流程如下。

(1)判断Bean名称生成器是否为空,如果不为空会进行单例Bean的注册操作。

(2)判断资源加载器是否为空,如果不为空会根据上下文类型做出不同的行为。当上下文类型是GenericApplicationContext时会设置上下文的资源加载器。当上下文类型是DefaultResourceLoader时会设置类加载器。

(3)判断是否需要添加转化换能器服务,如果需要会设置类型转换器,具体类型是ApplicationConversionService。

2.applyInitializers方法分析

这里将对applyInitializers方法进行分析,该方法主要用于处理ApplicationContextInitializer集合,具体处理代码如下:

@SuppressWarnings({"rawtypes", "unchecked"})
protected void applyInitializers(ConfigurableApplicationContext context) {
   // 获取ApplicationContextInitializer集合,这里获取的集合是排序后的集合
   for (ApplicationContextInitializer initializer :getInitializers()) {
      // 确认实际类型
      Class<?> requiredType =
GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
             ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

在applyInitializers方法中主要的处理流程如下:

(1)将成员变量initializers进行排序后得到ApplicationContextInitializer集合,这里的排序需要使用Order注解或者接口进行;

(2)获取ApplicationContextInitializer的实际类型,判断是否是ConfigurableApplication-Context的子类,如果不是则抛出异常;

(3)调用ApplicationContextInitializer所提供的initialize方法进行初始化操作。

3.load方法分析

这里将对load方法进行分析,该方法主要用于加载Bean实例,具体处理代码如下:

在load方法中主要的处理流程如下:

(1)从上下文中获取Bean定义注册器;

(2)通过第(1)步中获取的Bean定义注册器和需要注册的元数据创建BeanDefinition-Loader(Bean定义加载器)对象。

(3)如果Bean名称生成器不为空将其设置给Bean定义加载器;

(4)如果资源加载器不为空将其设置给Bean定义加载器;

(5)如果环境信息对象不为空将其设置给Bean定义加载器;

(6)通过Bean定义加载器将元数据进行加载。

在load方法中会涉及BeanDefinitionLoader来进行加载Bean定义的操作,有关Bean DefinitionLoader成员变量的详细内容见表2-3。

表2-3 BeanDefinitionLoader成员变量

在BeanDefinitionLoader中最为重要的是load方法族群,顶层入口代码如下:

void load() {
   for (Object source :this.sources) {
      load(source);
   }
}

在上述代码中会循环处理成员变量sources,该方法最终调用的load方法的细节代码如下:

在上述代码中会根据source对象的不同数据类型进行不同的load操作,在这里所使用的load方法本质就是使用成员变量中的不同的Bean定义读取器来进行加载操作。

2.3.6 refreshContext方法分析

本节将对refreshContext方法进行分析,该方法主要用于刷新上下文,具体处理代码如下:

private void refreshContext(ConfigurableApplicationContext context) {
   // 是否需要注册关闭的钩子
   if (this.registerShutdownHook) {
      try {
          // 上下文注册关闭的钩子
          context.registerShutdownHook();
      } catch (AccessControlException ex) {}
   }
   // 刷新操作
   refresh((ApplicationContext) context);
}

在refreshContext方法中主要的两个处理步骤如下:

(1)通过上下文注册关闭钩子;

(2)上下文刷新。

在这两个步骤中都需要依赖上下文对象本身来处理,前者需要依赖registerShutdownHook方法,后者需要依赖refresh方法。这两个方法都属于Spring Framework框架不属于Spring Boot框架,故本节不做详细分析。

2.3.7 callRunners方法分析

本节将对callRunners方法进行分析,该方法主要用于执行CommandLineRunner接口或者ApplicationRunner接口,具体处理代码如下:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   // 需要执行的runner实例集合
   List<Object> runners = new ArrayList<>();
   // 从容器中提取ApplicationRunner
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   // 从容器中提取CommandLineRunner
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   // 排序
   AnnotationAwareOrderComparator.sort(runners);
   // 循环runner并且执行
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
          callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
          callRunner((CommandLineRunner) runner, args);
      }
   }
}

在callRunners方法中主要处理流程如下:

(1)创建需要执行的runner实例集合;

(2)从容器中分别提取类型是ApplicationRunner或者类型是CommandLineRunner的Bean实例放入runner实例集合中;

(3)对runner实例集合进行排序;

(4)遍历所有runner实例并调用其run方法。 J5081qeTB3CIzEl0GoJ1S9pcq0lN8cY63NbA27DBhlzAE77+eqjs36wfFX//XIyG

点击中间区域
呼出菜单
上一章
目录
下一章
×