springboot源码分析11-ApplicationContextInitializer原理

简介: 摘要:springboot源码分析10-ApplicationContextInitializer使用一文中,我们详细地讲解了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.namevcap.application.namespring.config.namevcap.application.instance_index

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

vcap.application.instance_indexspring.application.indexserver.portPORT

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相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 


相关文章
|
1月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
36 0
|
4天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
1月前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
59 17
|
1月前
|
Java 容器
springboot自动配置原理
启动类@SpringbootApplication注解下,有三个关键注解 (1)@springbootConfiguration:表示启动类是一个自动配置类 (2)@CompontScan:扫描启动类所在包外的组件到容器中 (3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效
|
5月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
130 0
|
2月前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
121 4
|
3月前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot
|
3月前
|
Java 开发者 数据格式
【Java笔记+踩坑】SpringBoot基础4——原理篇
bean的8种加载方式,自动配置原理、自定义starter开发、SpringBoot程序启动流程解析
【Java笔记+踩坑】SpringBoot基础4——原理篇
|
3月前
|
Java 开发者 Spring
Springboot中ApplicationContextInitializer的使用及源码分析
`ApplicationContextInitializer` 是 Spring Boot 提供的一个强大的接口,它允许开发者有机会在应用上下文刷新之前,注入自定义的启动逻辑或预处理。通过合理地利用这个接口,可以提升应用的可配置性和灵活性。了解如何运用 `ApplicationContextInitializer` 可以让你在 Spring Boot 应用的早期启动阶段,有更多控制权。
138 4