code-learning/springboot/12-Spring Boot 源码分析-ApplicationContextInitializer.md

789 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 Spring Boot 源码分析 —— ApplicationContextInitializer
# 1. 概述
本文,我们来补充 [《精尽 Spring Boot 源码分析 —— SpringApplication》](http://svip.iocoder.cn/Spring-Boot/SpringApplication) 文章,并未详细解析的 ApplicationContextInitializer 。
# 2. ApplicationContextInitializer
`org.springframework.context.ApplicationContextInitializer` ApplicationContext 初始化接口。代码如下:
```
/**
* Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
* prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
*
* <p>Typically used within web applications that require some programmatic initialization
* of the application context. For example, registering property sources or activating
* profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
* context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
* for declaring a "contextInitializerClasses" context-param and init-param, respectively.
*
* <p>{@code ApplicationContextInitializer} processors are encouraged to detect
* whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
* implemented or if the @{@link org.springframework.core.annotation.Order Order}
* annotation is present and to sort instances accordingly if so prior to invocation.
*
* @author Chris Beams
* @since 3.1
* @param <C> the application context type
* @see org.springframework.web.context.ContextLoader#customizeContext
* @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
* @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
* @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
*/
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
```
- 1、ApplicationContextInitializer 是 Spring Framework 3.1 版本开始提供的接口。而本文,我们是来分享 Spring Boot 中,几个 ApplicationContextInitializer 实现类。
- 2、【作用】ApplicationContextInitializer 是一个回调接口,用于 Spring ConfigurableApplicationContext 容器执行 `#refresh()` 方法进行初始化之前,提前走一些自定义的初始化逻辑。
- 3、【场景】它的使用场景例如说 Web 应用中需要注册属性,或者激活 Profiles 。
- 4、【排序】它支持 Spring 的 Ordered 接口、`@Order` 注解,来对多个 ApplicationContextInitializer 实例进行排序从而实现ApplicationContextInitializer 按照顺序调用 `#initialize(C applicationContext)` 方法,进行初始化。
# 3. SpringApplication 中的使用
## 3.1 初始化 ApplicationContextInitializer 集合
在 SpringApplication 构造方法中,会调用 `#getSpringFactoriesInstances(Class<T> type)` 方法,获得 ApplicationContextInitializer 集合。代码如下:
```
// SpringApplication.java
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// <1> 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// <2> 创建对象们
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// <3> 排序对象们
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
```
- ```
<1>
```
处,加载指定 ApplicationContextInitializer 类型对应的,在
```
META-INF/spring.factories
```
里的类名的数组。
- 假设只在 Spring MVC 的环境下,`initializers` 属性的结果如下图:[![`initializers` 属性](12-Spring Boot 源码分析-ApplicationContextInitializer.assets/01.jpg)](http://static.iocoder.cn/images/Spring-Boot/2021-01-07/01.jpg)`initializers` 属性
- 艿艿整理了 Spring Boot 中ApplicationContextInitializer 的实现类们,如下图所示:[![Spring Boot ApplicationContextInitializer 的实现类](12-Spring Boot 源码分析-ApplicationContextInitializer.assets/01-17199767902571.jpg)](http://static.iocoder.cn/images/Spring-Boot/2021-01-22/01.jpg)Spring Boot ApplicationContextInitializer 的实现类
- `<2>` 处,创建对象们。
- `<3>` 处,排序对象们。这个就是在 [「2. ApplicationContextInitializer」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#) 提到的【排序】。
## 3.2 prepareContext
在 `#prepareContext(...)` 方法中,即在 Spring IoC 容器初始化之前,会调用 `#applyInitializers()` 方法,逐个调用 ApplicationContextInitializer 的初始化方法。代码如下:
```
// SpringApplication.java
protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍历 ApplicationContextInitializer 数组
for (ApplicationContextInitializer initializer : getInitializers()) {
// 校验 ApplicationContextInitializer 的泛型非空
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 初始化 ApplicationContextInitializer
initializer.initialize(context);
}
}
```
- 比较简单,就是调用 `ApplicationContextInitializer#initialize(context)` 方法,进行初始化。
下面,我们来逐个看看 Spring Boot 对 ApplicationContextInitializer 的实现类们。
# 4. DelegatingApplicationContextInitializer
`org.springframework.boot.context.config.DelegatingApplicationContextInitializer` ,实现 ApplicationContextInitializer、Ordered 接口,根据环境变量配置的 `context.initializer.classes` 配置的 ApplicationContextInitializer 类们,交给它们进行初始化。
## 4.1 构造方法
```
// DelegatingApplicationContextInitializer.java
/**
* 环境变量配置的属性
*/
private static final String PROPERTY_NAME = "context.initializer.classes";
/**
* 默认优先级
*/
private int order = 0;
// ... 构造方法为空
@Override
public int getOrder() {
return this.order;
}
```
- 优先级为 0 ,在 Spring Boot 默认的 ApplicationContextInitializer 实现类中,是排在最前面的。
## 4.2 initialize
实现 `#initialize(ConfigurableApplicationContext context)` 方法,代码如下:
```
// DelegatingApplicationContextInitializer.java
@Override
public void initialize(ConfigurableApplicationContext context) {
// <1> 获得环境变量配置的 ApplicationContextInitializer 集合们
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
// 如果非空,则进行初始化
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
```
- ```
<1>
```
处,调用
```
#getInitializerClasses(ConfigurableEnvironment env)
```
方法,获得环境变量配置的 ApplicationContextInitializer 集合们。代码如下:
```
// DelegatingApplicationContextInitializer.java
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;
}
private Class<?> getInitializerClass(String className) throws LinkageError {
try {
// 获得全类名,对应的类
Class<?> initializerClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, initializerClass);
return initializerClass;
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
}
}
```
- `<2>` 处,调用 `#applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses)` 方法,执行初始化。代码如下:
```
// DelegatingApplicationContextInitializer.java
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
Class<?> contextClass = context.getClass();
// 遍历 initializerClasses 数组,创建对应的 ApplicationContextInitializer 对象们 ①
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
for (Class<?> initializerClass : initializerClasses) {
initializers.add(instantiateInitializer(contextClass, initializerClass));
}
// 执行 ApplicationContextInitializer 们的初始化逻辑 ②
applyInitializers(context, initializers);
}
// 被 ① 处调用
private ApplicationContextInitializer<?> instantiateInitializer(Class<?> contextClass, Class<?> initializerClass) {
// 断言校验
Class<?> requireContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
Assert.isAssignable(requireContextClass, contextClass, String.format(
"Could not add context initializer [%s]"
+ " as its generic parameter [%s] is not assignable "
+ "from the type of application context used by this "
+ "context loader [%s]: ",
initializerClass.getName(), requireContextClass.getName(), contextClass.getName()));
// 创建 ApplicationContextInitializer 对象
return (ApplicationContextInitializer<?>) BeanUtils.instantiateClass(initializerClass);
}
// 被 ② 处调用
@SuppressWarnings({ "unchecked", "rawtypes" })
private void applyInitializers(ConfigurableApplicationContext context, List<ApplicationContextInitializer<?>> initializers) {
// 排序,无处不在的排序!
initializers.sort(new AnnotationAwareOrderComparator());
// 执行初始化逻辑
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
}
```
- 虽然代码有点长,但是简单的。
# 5. SharedMetadataReaderFactoryContextInitializer
`org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer` ,实现 ApplicationContextInitializer、Ordered 接口,它会创建一个用于在 ConfigurationClassPostProcessor 和 Spring Boot 间共享的 CachingMetadataReaderFactory Bean 对象。
简化代码如下:
```
/**
* {@link ApplicationContextInitializer} to create a shared
* {@link CachingMetadataReaderFactory} between the
* {@link ConfigurationClassPostProcessor} and Spring Boot.
*
* @author Phillip Webb
* @since 1.4.0
*/
/**
* 创建的 CachingMetadataReaderFactory 的 Bean 名字
*/
public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."
+ "internalCachingMetadataReaderFactory";
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor());
}
@Override
public int getOrder() {
return 0;
}
// ... 省略 CachingMetadataReaderFactoryPostProcessor 内部类
```
- 看不懂这个类的具体用途,暂时先不去深究。😈 等真的需要它时,咱们在细细的撸它。
# 6. ContextIdApplicationContextInitializer
`org.springframework.boot.context.ContextIdApplicationContextInitializer` ,实现 ApplicationContextInitializer、Ordered 接口,负责生成 Spring 容器的编号。
## 6.1 构造方法
```
// ContextIdApplicationContextInitializer.java
/**
* 优先级
*/
private int order = Ordered.LOWEST_PRECEDENCE - 10;
@Override
public int getOrder() {
return this.order;
}
```
## 6.2 initialize
实现 `#initialize(ConfigurableApplicationContext context)` 方法,代码如下:
```
// ContextIdApplicationContextInitializer.java
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// <1> 获得(创建) ContextId 对象
ContextId contextId = getContextId(applicationContext);
// <2> 设置到 applicationContext 中
applicationContext.setId(contextId.getId());
// <3> 注册到 contextId 到 Spring 容器中
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}
```
- ContextId ,是 ContextIdApplicationContextInitializer 的内部类Spring 容器编号的封装。代码如下:
```
// ContextIdApplicationContextInitializer#ContextId.java
class ContextId {
/**
* 递增序列
*/
private final AtomicLong children = new AtomicLong(0);
/**
* 编号
*/
private final String id;
ContextId(String id) {
this.id = id;
}
/**
* @return 创建子 Context 的编号
*/
ContextId createChildId() {
return new ContextId(this.id + "-" + this.children.incrementAndGet());
}
String getId() {
return this.id;
}
}
```
- `<1>` 处,调用 `#getContextId(ConfigurableApplicationContext applicationContext)` 方法,获得(创建) ContextId 对象。代码如下:
```
// ContextIdApplicationContextInitializer.java
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
// 获得父 ApplicationContext 对象
ApplicationContext parent = applicationContext.getParent();
// 情况一,如果父 ApplicationContext 存在,且有对应的 ContextId 对象,则使用它生成当前容器的 ContextId 对象
if (parent != null && parent.containsBean(ContextId.class.getName())) {
return parent.getBean(ContextId.class).createChildId();
}
// 情况二,创建 ContextId 对象
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.getProperty("spring.application.name");
return StringUtils.hasText(name) ? name : "application";
}
```
- 一般情况下,使用 `"spring.application.name"` 环境变量,作为 ContextId 对象的 `id` 属性。
- `<2>` 处,设置到 `applicationContext.id` 中。
- `<3>` 处,注册到 `contextId` 到 Spring 容器中。这样,后续就可以拿到了。
# 7. ConfigurationWarningsApplicationContextInitializer
> 艿艿:对于这个类,选择性了解即可。
`org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer` ,实现 ApplicationContextInitializer 接口,用于检查配置,报告错误的配置。如下是其类上的注释:
```
/**
* {@link ApplicationContextInitializer} to report warnings for common misconfiguration mistakes.
*/
```
## 7.1 initialize
实现 `#initialize(ConfigurableApplicationContext applicationContext)` 方法,代码如下:
```
// ConfigurationWarningsApplicationContextInitializer.java
@Override
public void initialize(ConfigurableApplicationContext context) {
// 注册 ConfigurationWarningsPostProcessor 到 Spring 容器中
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
```
- 注册 ConfigurationWarningsPostProcessor 到 Spring 容器中。关于 ConfigurationWarningsPostProcessor 类,在 [「7.2 ConfigurationWarningsPostProcessor」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#) 中,详细解析。
- 其中,`#getChecks()` 方法,返回 Check 数组。代码如下:
```
// ConfigurationWarningsApplicationContextInitializer.java
protected Check[] getChecks() {
return new Check[] { new ComponentScanPackageCheck() };
}
```
- 返回的数组,只有一个 ComponentScanPackageCheck 对象。
- Check ,是 ConfigurationWarningsApplicationContextInitializer 的内部接口,校验器。代码如下:
```
// ConfigurationWarningsApplicationContextInitializer.java
@FunctionalInterface
protected interface Check {
/**
* Returns a warning if the check fails or {@code null} if there are no problems.
* @param registry the {@link BeanDefinitionRegistry}
* @return a warning message or {@code null}
*/
String getWarning(BeanDefinitionRegistry registry);
}
```
- 看到此处,胖友可能有点懵逼,不着急。在 [「7.2 ConfigurationWarningsPostProcessor」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#) 中,会串起来滴。
## 7.2 ConfigurationWarningsPostProcessor
ConfigurationWarningsPostProcessor ,是 ConfigurationWarningsApplicationContextInitializer 的内部静态类,实现 PriorityOrdered、BeanDefinitionRegistryPostProcessor 接口,代码如下:
```
// ConfigurationWarningsPostProcessor#ConfigurationWarningsPostProcessor.java
protected static final class ConfigurationWarningsPostProcessor
implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {
/**
* Check 数组
*/
private Check[] checks;
public ConfigurationWarningsPostProcessor(Check[] checks) {
this.checks = checks;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 遍历 Check 数组,执行校验。若有错,则打印 warn 日志
for (Check check : this.checks) {
String message = check.getWarning(registry);
if (StringUtils.hasLength(message)) {
warn(message);
}
}
}
private void warn(String message) {
if (logger.isWarnEnabled()) {
logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
}
}
}
```
- 核心就是 `#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)` 方法。在其内部,遍历 Check 数组,执行校验。若有错,则打印 warn 日志。在上文中,我们看到目前 `checks` 只有一个 ComponentScanPackageCheck 元素。关于它,我们在 [「7.3 ComponentScanPackageCheck」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#) 详细解析。
## 7.3 ComponentScanPackageCheck
ComponentScanPackageCheck ,实现 Check 接口,检查是否使用了 `@ComponentScan` 注解,扫描了指定扫描的包。
### 7.3.1 构造方法
```
// ConfigurationWarningsPostProcessor#ComponentScanPackageCheck.java
/**
* 有问题的包的集合。
*
* 即禁止使用 @ComponentScan 注解扫描这个集合中的包
*/
private static final Set<String> PROBLEM_PACKAGES;
static {
Set<String> packages = new HashSet<>();
packages.add("org.springframework");
packages.add("org");
PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);
}
```
- 即禁止扫描 `"org.springframework"` 和 `"org"` 包。因为 `"org.springframework"` 包下,有非常多的 Bean ,这样扫描,会错误的注入很多 Bean 。
### 7.3.2 getWarning
实现 `#getWarning(BeanDefinitionRegistry registry)` 方法,代码如下:
```
// ConfigurationWarningsPostProcessor#ComponentScanPackageCheck.java
@Override
public String getWarning(BeanDefinitionRegistry registry) {
// <1> 获得要扫描的包
Set<String> scannedPackages = getComponentScanningPackages(registry);
// <2> 获得要扫描的包中,有问题的包
List<String> problematicPackages = getProblematicPackages(scannedPackages);
// <3.1> 如果 problematicPackages 为空,说明不存在问题
if (problematicPackages.isEmpty()) {
return null;
}
// <3.2> 如果 problematicPackages 非空,说明有问题,返回错误提示
return "Your ApplicationContext is unlikely to "
+ "start due to a @ComponentScan of "
+ StringUtils.collectionToDelimitedString(problematicPackages, ", ")
+ ".";
}
```
- `<1>` 处,调用 `#getComponentScanningPackages(BeanDefinitionRegistry registry)` 方法,获得要扫描的包。代码如下:
```
// ConfigurationWarningsPostProcessor#ComponentScanPackageCheck.java
protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {
// 扫描的包的集合
Set<String> packages = new LinkedHashSet<>();
// 获得所有 BeanDefinition 的名字们
String[] names = registry.getBeanDefinitionNames();
for (String name : names) {
// 如果是 AnnotatedBeanDefinition
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
// 如果有 @ComponentScan 注解,则添加到 packages 中
addComponentScanningPackages(packages, annotatedDefinition.getMetadata());
}
}
return packages;
}
private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {
// 获得 @ComponentScan 注解
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));
// 如果存在,则添加到 packages 中
if (attributes != null) {
addPackages(packages, attributes.getStringArray("value"));
addPackages(packages, attributes.getStringArray("basePackages"));
addClasses(packages, attributes.getStringArray("basePackageClasses"));
if (packages.isEmpty()) {
packages.add(ClassUtils.getPackageName(metadata.getClassName()));
}
}
}
private void addPackages(Set<String> packages, String[] values) {
if (values != null) {
Collections.addAll(packages, values);
}
}
private void addClasses(Set<String> packages, String[] values) {
if (values != null) {
for (String value : values) {
packages.add(ClassUtils.getPackageName(value));
}
}
}
```
- 虽然很长,但是比较简单。就是找 `@ComponentScan` 注解所扫描的包。
- `<2>` 处,调用 `#getProblematicPackages(Set<String> scannedPackages)` 方法,获得要扫描的包中,有问题的包。代码如下:
```
// ConfigurationWarningsPostProcessor#ComponentScanPackageCheck.java
private List<String> getProblematicPackages(Set<String> scannedPackages) {
// 有问题的包的集合
List<String> problematicPackages = new ArrayList<>();
for (String scannedPackage : scannedPackages) {
// 判断是否在 PROBLEM_PACKAGES 中。如果是,则添加到 problematicPackages 中
if (isProblematicPackage(scannedPackage)) {
problematicPackages.add(getDisplayName(scannedPackage));
}
}
return problematicPackages;
}
private boolean isProblematicPackage(String scannedPackage) {
if (scannedPackage == null || scannedPackage.isEmpty()) {
return true;
}
return PROBLEM_PACKAGES.contains(scannedPackage);
}
private String getDisplayName(String scannedPackage) {
if (scannedPackage == null || scannedPackage.isEmpty()) {
return "the default package";
}
return "'" + scannedPackage + "'";
}
```
- 😈 就是判断 `scannedPackages` 哪些在 `PROBLEM_PACKAGES` 中。
- `<3.1>` 处,如果 `problematicPackages` 为空,说明不存在问题。
- `<3.2>` 处,如果 `problematicPackages` 非空,说明有问题,返回错误提示。
# 8. ServerPortInfoApplicationContextInitializer
`org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer` ,实现 ApplicationContextInitializer、ApplicationListener 接口,监听 EmbeddedServletContainerInitializedEvent 类型的事件,然后将内嵌的 Web 服务器使用的端口给设置到 ApplicationContext 中。
## 8.1 initialize
实现 `#initialize(ConfigurableApplicationContext applicationContext)` 方法,代码如下:
```
// ServerPortInfoApplicationContextInitializer.java
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(this);
}
```
- 将自身作为一个 ApplicationListener 监听器,添加到 Spring 容器中。
## 8.2 onApplicationEvent
实现 `#onApplicationEvent(WebServerInitializedEvent event)` 方法,当监听到 WebServerInitializedEvent 事件,进行触发。代码如下:
```
// ServerPortInfoApplicationContextInitializer.java
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// <1> 获得属性名
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
// <2> 设置端口到 environment 的 propertyName 中
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
```
- `<1>` 处,获得属性名。其中,`#getName(WebServerApplicationContext context)` 方法,获得 WebServer 的名字。代码如下:
```
// ServerPortInfoApplicationContextInitializer.java
private String getName(WebServerApplicationContext context) {
String name = context.getServerNamespace();
return StringUtils.hasText(name) ? name : "server";
}
```
- `<2>` 处,调用 `#setPortProperty(ApplicationContext context, String propertyName, int port)` 方法,设置端口到 `environment` 的 `propertyName` 中。代码如下:
```
// ServerPortInfoApplicationContextInitializer.java
private void setPortProperty(ApplicationContext context, String propertyName, int port) {
// 设置端口到 environment 的 propertyName 中
if (context instanceof ConfigurableApplicationContext) {
setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port);
}
// 如果有父容器,则继续设置
if (context.getParent() != null) {
setPortProperty(context.getParent(), propertyName, port);
}
}
@SuppressWarnings("unchecked")
private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) {
MutablePropertySources sources = environment.getPropertySources();
// 获得 "server.ports" 属性对应的值
PropertySource<?> source = sources.get("server.ports");
if (source == null) {
source = new MapPropertySource("server.ports", new HashMap<>());
sources.addFirst(source);
}
// 添加到 source 中
((Map<String, Object>) source.getSource()).put(propertyName, port);
}
```
- 注意噢,设置的属性结果是,`"server.ports"` 中,的 KEY 为 `propertyName` VALUE 为 `port` 。😈
# 9. 其他 ApplicationContextInitializer 实现类
ApplicationContextInitializer 还有一些其它实现类,不是很重要,可以选择不看。
- ```
spring-boot-test
```
模块
- `org.springframework.boot.test.context.ConfigFileApplicationContextInitializer` 类
- `org.springframework.boot.test.context.SpringBootContextLoader` 中的 ParentContextApplicationContextInitializer 类
- ```
spring-boot-devtools
```
模块
- `org.springframework.boot.devtools.restart.RestartScopeInitializer` 类
- ```
spring-boot-autoconfigure
```
模块
- `org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener` 类
- ```
spring-boot
```
模块
- `org.springframework.boot.web.servlet.support.ServletContextApplicationContextInitializer` 类
- `org.springframework.boot.builder.ParentContextApplicationContextInitializer` 类
😈 貌似还是蛮多的。
# 666. 彩蛋
小更一下,算是水文~感觉比较有收获的 ApplicationContextInitializer 的实现类是:
- [「6. ContextIdApplicationContextInitializer」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#)
- [「8. ServerPortInfoApplicationContextInitializer」](https://svip.iocoder.cn/Spring-Boot/ApplicationContextInitializer/#)
参考和推荐如下文章:
- dm_vincent [《[Spring Boot\] 5. Spring Boot 中的ApplicationContext - 执行 ApplicationContextInitializer 初始化器》](https://blog.csdn.net/dm_vincent/article/details/77619780)