摘要: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注解),会排在最后,因为这类对象的优先级是最低的
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值。该类会尝试读取如下的属性:
- spring.application.name
- vcap.application.name
- spring.config.name
- vcap.application.instance_index
- spring.application.index
- server.port
- PORT
- 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相关原创技术干货。
扫一扫下方二维码或者长按识别二维码,即可关注。