本节将对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方法。
本节将对createBootstrapContext方法进行分析,具体处理代码如下:
private DefaultBootstrapContext createBootstrapContext() { // 创建默认的引导上下文 DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); // 循环调度BootstrapRegistryInitializer的initialize方法 this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext)); // 返回默认的引导上下文 return bootstrapContext; }
createBootstrapContext方法的主要目的是创建默认的引导上下文,具体执行流程如下:
(1)创建默认的引导上下文;
(2)通过成员变量bootstrapRegistryInitializers初始化第(1)步创建的引导上下文。
本节将对prepareEnvironment方法进行分析,该方法的主要目标是进行环境对象的准备,具体处理代码如下:
在prepareEnvironment方法中主要的处理流程如下:
(1)获取环境对象,关于环境对象的获取提供了两种方式,第一种是通过成员变量获取,第二种是根据应用类型进行创建,创建StandardServletEnvironment、StandardReactiveWebEnvironment和StandardEnvironment的具体处理方法是getOrCreateEnvironment;
(2)配置环境信息,具体处理方法是configureEnvironment;
(3)追加或者移除部分属性,具体处理方法是ConfigurationPropertySources.attach;
(4)触发环境准备事件,具体处理交给SpringApplicationRunListeners进行负责;
(5)将环境对象中的属性表进行重排序。排序规则:从属性表中移除defaultProperties名称的属性,如果该属性不为空则放入最后一个索引位上;
(6)绑定环境,具体处理方法是bindToSpringApplication;
(7)判断是否是自定义环境对象,如果是自定义则会进行配置对象转换,并且在转换后再次进行第(3)步的操作流程,操作完成后返回。
这里将对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。
这里将对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)判断环境变量中的属性源对象是否包含键数据,如果不包含会直接进行头插法加入数据,如果包含会进行历史数据和新数据的合并操作,在合并后将环境对象中的属性源进行替换。
这里将对ConfigurationPropertySources.attach方法进行分析,具体处理代码如下:
在上述代码中主要的处理流程如下:
(1)从环境对象中获取属性表;
(2)获取configurationProperties属性,获取后变量名为attached;
(3)如果变量attached不为空并且attached中的源对象不等于第(1)步中获取的数据,则会将configurationProperties名称的属性移除并且将attached置为null;
(4)如果attached变量为空,则将进行头插法加入属性源数据信息。
在前文关于环境对象准备时需要使用一个叫作环境转换的对象,它是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)步中的创建结果。
本节将对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属性。
本节将对printBanner方法进行分析,具体处理代码如下:
在上述代码中主要的处理流程如下:
(1)判断是否开启banner,如果未开启将返回null;
(2)获取资源加载接口;
(3)通过资源加载器和成员变量banner创建SpringApplicationBannerPrinter;
(4)判断是否需要输出到日志文件,如果需要则直接进行日志写出,如果不需要将通过System.out写出到控制台。
本节将对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三个方法进行分析。
下面将对postProcessApplicationContext方法进行分析,该方法可以通过子类来进行更新,这里主要讨论的是SpringApplication中的postProcessApplicationContext方法,具体处理代码如下:
在postProcessApplicationContext方法中主要的处理流程如下。
(1)判断Bean名称生成器是否为空,如果不为空会进行单例Bean的注册操作。
(2)判断资源加载器是否为空,如果不为空会根据上下文类型做出不同的行为。当上下文类型是GenericApplicationContext时会设置上下文的资源加载器。当上下文类型是DefaultResourceLoader时会设置类加载器。
(3)判断是否需要添加转化换能器服务,如果需要会设置类型转换器,具体类型是ApplicationConversionService。
这里将对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方法进行初始化操作。
这里将对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定义读取器来进行加载操作。
本节将对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框架,故本节不做详细分析。
本节将对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方法。