springboot源码分析11-ApplicationContextInitializer原理

2017-12-07 bestshtesting

摘要: 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实力对象最终会存储在 S pringApplication类中的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最终将存储到S pringApplication类中的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、 S pringApplication类中的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相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。


用户评论
开源开发学习小组列表