springboot源码分析11-ApplicationContextInitializer原理

摘要:springboot源码分析10-ApplicationContextInitializer使用一文中,我们详细地讲解了ApplicationContextInitializer的三种使用方式,本文我们重点看一下为何这三种方式都可以使用,也就是框架是如何处理的。包括内置的ContextIdApplicationContextInitializer、DelegatingApplicationContextInitializer。

1.1. 用户手动添加ApplicationContextInitializer

首先,我们回想一下ApplicationContextInitializer实现方式一,示例代码如下:

@SpringBootApplication

public class DemoApplication {

public static void main(String[] args) {

SpringApplication springApplication = new SpringApplication(DemoApplication.class);

springApplication.addInitializers(new ShareniuApplicationContextInitializer());

ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args);

}

}

我们重点看一下springApplication.addInitializers方法如下所示:

private List<ApplicationContextInitializer<?>> initializers;

public void addInitializers(ApplicationContextInitializer<?>... initializers) {

this.initializers.addAll(Arrays.asList(initializers));

}

上述中的ShareniuApplicationContextInitializer实力对象最终会存储在SpringApplication类中的initializers集合中。这个集合在哪里进行调用的呢?我们不禁有个疑问?文章稍后我们再过来看这个问题。

1.2. 系统内置的ApplicationContextInitializer

上文的代码中,实例化了SpringApplication类,该类的构造函数代码如下:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

...

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

}

上述的代码逻辑中就涉及到了系统内置的一系列上下文初始化器的获取以及添加,我们看一下getSpringFactoriesInstances(ApplicationContextInitializer.class)方法,相信只要认真看完前面的系列文章的朋友,就可以很快的知道这行代码的含义就是加载META-INF/spring.factories文件key为org.springframework.context.ApplicationContextInitializer的所有属性值,因此springboot源码分析10-ApplicationContextInitializer使用一文中的使用方式三就不难理解了。关于getSpringFactoriesInstances方法的相关执行逻辑可以参考springboot源码分析4-springboot之SpringFactoriesLoader使用

spring-boot-2.0.0.M6.jar中META-INF/spring.factories文件key为org.springframework.context.ApplicationContextInitializer的所有属性值如下所示:

org.springframework.context.ApplicationContextInitializer=\

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\

org.springframework.boot.context.ContextIdApplicationContextInitializer,\

org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\

org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

上述的一系列逻辑执行完毕之后,所有的ApplicationContextInitializer最终将存储到SpringApplication类中的initializers集合中。

1.3. 触发ApplicationContextInitializer

接下来,我们继续讲问题回归到springApplication类中的run方法中,相关代码如下所示:

public ConfigurableApplicationContext run(String... args) {

...

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

...

}

prepareContext方法的核心代码如下:

private void prepareContext(ConfigurableApplicationContext context,

ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {

...

applyInitializers(context);

...

}

prepareContext方法的各种初始化逻辑非常的复杂,因此这里我们才暂时先将关于ApplicationContextInitializer有关的代码罗列出来,防止一次性罗列之后,歪楼跑题。applyInitializers方法的实现逻辑如下:

protected void applyInitializers(ConfigurableApplicationContext context) {

for (ApplicationContextInitializer initializer : getInitializers()) {

Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(

initializer.getClass(), ApplicationContextInitializer.class);

initializer.initialize(context);

}

}

 

上述代码的处理逻辑如下:

1、从SpringApplication类中的initializers集合获取所有的ApplicationContextInitializer。也就是getInitializers()函数所做的事情。

2、循环调用ApplicationContextInitializer中的initialize方法。

讲解到这里之后,对于ApplicationContextInitializer中的使用方式1、3已经非常清楚了,那么通过在配置文件中配置context.initializer.classes进而设置ApplicationContextInitializer的方式貌似还没有看到踪迹(方式2)?上文中的initializers集合已经初始化了,然而方式2中的具体ApplicationContextInitializer并没有被添加到initializers集合中,这又是怎么回事呢?我们不妨看看一系列重要的内置ApplicationContextInitializer。

1.4. ApplicationContextInitializer集合排序

所有的ApplicationContextInitializer均可以实现order接口进行优先级的设置。

1. 基于Order值升序排序,反应的就是优先级的从高到底

2. 对于拥有相同Order值的对象,任意顺序

3. 对于不能排序的对象(没有实现Ordered接口,没有@Order注解或者@Priority注解),会排在最后,因为这类对象的优先级是最低的

具体的实现可以去看源代码,就不贴在这里了。AnnotationAwareOrderComparator扩展自OrderComparator,从而能够支持@Order注解以及javax.annotation.Priority注解。OrderComparator已经可以支持Ordered接口了。

1.5. DelegatingApplicationContextInitializer集合排序

DelegatingApplicationContextInitializer:顾名思义,这个初始化器实际上将初始化的工作委托给context.initializer.classes环境变量指定的初始化器(通过类名),也就是上文中提到的方式2内部实现机制。

该类的核心代码如下所示:

private static final String PROPERTY_NAME = "context.initializer.classes";

public void initialize(ConfigurableApplicationContext context) {

ConfigurableEnvironment environment = context.getEnvironment();

List<Class<?>> initializerClasses = getInitializerClasses(environment);

if (!initializerClasses.isEmpty()) {

applyInitializerClasses(context, initializerClasses);

}

}

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {

String classNames = env.getProperty(PROPERTY_NAME);

List<Class<?>> classes = new ArrayList<>();

if (StringUtils.hasLength(classNames)) {

for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {

classes.add(getInitializerClass(className));

}

}

return classes;

}

上述的代码处理逻辑如下:

1、通过env获取到context.initializer.classes配置的值,如果有则直接获取到具体的值并进行实例化。

2、开始调用具体ApplicationContextInitializer类中的initialize方法。

这个初始化器的优先级是Spring Boot定义的4个初始化器中优先级别最高的,因此会被第一个执行。

1.6. ContextIdApplicationContextInitializer

这个类的作用是给ApplicationContext(上下文对象)设置一个id值。该类会尝试读取如下的属性:

  1. spring.application.name
  2.  vcap.application.name
  3.  spring.config.name
  4.  vcap.application.instance_index
  5.  spring.application.index
  6. server.port
  7.  PORT
  8. spring.profiles.active

该类的核心代码如下:

private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";

private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}";

private String getApplicationId(ConfigurableEnvironment environment) {

String name = environment.resolvePlaceholders(this.name);

String index = environment.resolvePlaceholders(INDEX_PATTERN);

String profiles = StringUtils.arrayToCommaDelimitedString(environment.getActiveProfiles());

if (StringUtils.hasText(profiles)) {

name = name + ":" + profiles;

}

if (!"null".equals(index)) {

name = name + ":" + index;

}

return name;

}

上述的代码逻辑进行如下的总结:

1、首先获取名称。上述说的8个配置属性均可以使用spel表达式,因此这里进行了表达式的获取解析工作。也就是 environment.resolvePlaceholders所做的事情。名称的取值依赖如下几个属性。spring.application.name、vcap.application.name、spring.config.name、vcap.application.instance_index

2、获取索引。与name的获取道理一样,index的取值依赖如下几个属性。

vcap.application.instance_index、spring.application.index、server.port、PORT

3、获取spring.profiles.active的值,如果不为空,则name的值为name:spring.profiles.active的值.

4、如果index不为空,则最终name的值为name:spring.profiles.active的值:index的值。

下面我们通过一个例子进行详细地说明:

首先,我们在application.properties文件中配置如下几个属性:

spring.application.name=shareniu

spring.application.index=10001

spring.profiles.active=dev

书写一个测试类如下所示:

@SpringBootApplication

public class DemoApplication {

public static void main(String[] args) {

SpringApplication springApplication = new SpringApplication(DemoApplication.class);

springApplication.addInitializers(new ShareniuApplicationContextInitializer());

ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args);

String id = configurableApplicationContext.getId();

System.out.println("id:==================="+id);

}

}

运行上述的类,控制台的输出信息如下:

id:===================shareniu:dev:10001

关于给ApplicationContext(上下文对象)设置一个id值的高级用法,后续的实战章节中详尽的进行讲解。



欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。 

时间: 2024-09-15 00:59:28

springboot源码分析11-ApplicationContextInitializer原理的相关文章

springboot源码分析3-springboot之banner类架构以及原理

继续上文的<<springboot源码分析2-springboot 之banner定制以及原理章节>>进行讲解,上一节我们详细详解了banner的三种输出模式.banner的输出模式设置.banner类的架构.SpringApplicationBannerPrinter类.ImageBanner以及TextBanner的处理方式.本小节我们来重点讲解一下各种banner处理类的相关实现逻辑以及设计意图和职责. 1.1 SpringBootBanner类 SpringBootBann

springboot源码分析2-springboot 之banner定制以及原理

1. springboot源码分析2-springboot 之banner定制以及原理 springboot在启动的时候,默认会在控制台输出默认的banner.也就是我们经常所说的图案,输出的图案如下所示:   .   ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  \\/  ___)| |_)| | | |

springboot源码分析6-springboot之PropertySource类初探

摘要:本小节重点梳理一下PropertySource类的相关结构以及职责,本文的学习前提是学习了springboot源码分析5-springboot之命令行参数以及原理一文. 在springboot源码分析5-springboot之命令行参数以及原理一文中,我们看到了实例化Source类的时候,会去先实例化其父类SimpleCommandLinePropertySource.SimpleCommandLinePropertySource类的构造函数中直接解析了命令行参数以及值,然后返回封装好的C

springboot源码分析10-ApplicationContextInitializer使用

摘要:spring中ApplicationContextInitializer接口是在ConfigurableApplicationContext刷新之前初始化ConfigurableApplicationContext的回调接口.当spring框架内部执行 ConfigurableApplicationContext#refresh() 方法的时候回去回调. 1.1. 实现方式一 首先,我们需要自定义一个类并且实现ApplicationContextInitializer接口.示例代码如下:

springboot源码分析7-环境属性构造过程(上)

使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties.xml.yml.yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可.同理application.xml.applica

springboot源码分析14-ApplicationContextInitializer原理Springboot中PropertySource注解多环境支持以及原理

摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件.然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用.但是也遗留一个问题,PropertySource注解貌似是不支持多种环境的动态切换?这个问题该如何解决呢?我们需要从源码中看看他到底是否支持. 首先,我们开始回顾一下上节课说的PropertySource注解的

JVM源码分析之javaagent原理完全解读

前言 本系列文章都是基于Hotspot/JDK源码,从源码角度来分析我们常见的JVM参数,Java概念以及对应的实现原理及玩法等,希望从根本上来理清Java知识点,我们会不定期地分享这个系列的文章,这些文章可能源于最近碰到的问题,也可能是同学们的提问,甚至有可能是我们突然想到的话题等,有些东西我们现在可能也不一定清楚,但是我们非常愿意花时间去了解清楚并分享给大家. 概述 本文重点讲述javaagent的具体实现,因为它面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c

springboot源码分析9-random的使用以及原理

摘要:springboot框架为我们提供了很多的便利,其中有一个非常有意思的功能,那就是可以通过变量的方式来配置一个随机数random,然后使用random随机出各式各样数值.本位重点讲解一下random的使用以及框架内部的实现机制. 1.1. Springboot中random的使用 首先我们定义一个配置类,如下所示: 1 @Component 2 public class Config { 3  @Value("${random.value}") 4  private String

springboot源码分析4-springboot之SpringFactoriesLoader使用

摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用.在详细分析之前,我们可以思考一个问题?在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?其一:我们可以规范不同的系统调用,也就是传递一个系统标识:其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理:其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一