springboot之ApplicationContextInitializer分析

写在前面

这篇文章中我们详细分析了springboot通过main方法启动程序的过程,但是其中涉及到的ApplicationContextInitializer内容并没有详细说明,本文作为补充,对本部分内容进行分析。

1:说明

ApplicationContextInitializer是spring提供给我们针对ApplicationContext容器的一个扩展点,用于在对ApplicationContext容器刷新前进行一些设置,其源码如下:

package org.springframework.context;
// ConfigurableApplicationContext刷新前的回调接口,一般用在web环境中
// 针对ApplicationContext需要一些程序化定制的场景,比如设置Environment
// 的profile相关环境变量
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	// 初始化给定的applicationContext
	void initialize(C applicationContext);

}

从其包名org.springframework.context可以看出,这是spring提供的原生接口,而非springboot提供,在spring中其实并没有提供任何的实现类,从下图可以看出来这点:
在这里插入图片描述
我是在springboot环境中看的,所以只有springboot相关的具体实现类,因为该接口是spring设计用来给第三方框架扩展使用的,所以自己没有任何具体的实现也比较合理。

2:三种实现方式

2.1:通过main方法中添加

  • 代码
@SpringBootApplication
public class SpringbootHelloWorldApplication {

    public static void main(String[] args) {
        // SpringApplication.run(SpringbootHelloWorldApplication.class, args);
        SpringApplication springApplication = new SpringApplication(SpringbootHelloWorldApplication.class);
        /*springApplication.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {

            }
        });*/
        springApplication.addInitializers(ac -> System.out.println("initializer added by springboot main"));
        springApplication.run(args);
    }

}
  • 运行
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.1.14.RELEASE)

initializer added by springboot main

2.2:在配置文件中

  • 实现类
public class MyContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("initializer added by config file");
    }
}
  • 配置
    application.properties文件中配置如下内容:
    context.initializer.classes=dongshi.daddy.contextinitializer.MyContextInitializer
  • 运行
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.1.14.RELEASE)

initializer added by config file

2.3:SpringBoot的SPI

  • 实现类
public class MyContextInitializer1 implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("initializer added by springboot spi");
    }
}
  • 配置
    META-INF/spring.factories文件中配置如下内容:
    在这里插入图片描述
  • 运行

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.1.14.RELEASE)

initializer added by springboot spi

3:哪里调用的?

结合这篇文章可以很容易知道,调用是在方法org.springframework.boot.SpringApplication#prepareContext,源码如下:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	...snip...
	// <202106071723>
	applyInitializers(context);
	...snip...
}

<202106071723>处源码如下:

org.springframework.boot.SpringApplication#applyInitializers
// 在configurable application context刷新之前,应用所有的ApplicationContextInitializer
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
	// 获取所有的ApplicationContextInitializer,并循环调用其initialize方法
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

接下来我们看下springboot中提供的具体实现类及其作用。

4:具体的实现类

4.1:DelegatingApplicationContextInitializer

该类是通过springboot SPI配置的,如下图:
在这里插入图片描述
我们在2.2:在配置文件中中分析了在配置文件中通过属性context.initializer.classes注册自定义ApplicationContextInitializer,DelegatingApplicationContextInitializer的作用正是读取这些配置信息,并通过调用其initialize方法使其生效,看下源码:

org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize
@Override
public void initialize(ConfigurableApplicationContext context) {
	// 获取environment,用于获取属性信息
	ConfigurableEnvironment environment = context.getEnvironment();
	// <202106071755>
	List<Class<?>> initializerClasses = getInitializerClasses(environment);
	if (!initializerClasses.isEmpty()) {
		// <202106071757>
		applyInitializerClasses(context, initializerClasses);
	}
}

<202106071755>处是获取所有通过context.initializer.classes配置的属性值,源码如下:

org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses
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;
}

不是很难,自己看下。
<202106071757>处源码如下:

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
	Class<?> contextClass = context.getClass();
	// 实例对象集合
	List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
	// 循环通过class创建实例对象,并添加到集合中
	for (Class<?> initializerClass : initializerClasses) {
		initializers.add(instantiateInitializer(contextClass, initializerClass));
	}
	// <202106071758>
	applyInitializers(context, initializers);
}

<202106071758>处是关键代码,应用AppplicationContextInitializer,源码如下:

org.springframework.boot.context.config.DelegatingApplicationContextInitializer#applyInitializers
private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
	// 排序
	initializers.sort(new AnnotationAwareOrderComparator());
	// 循环调用initialize方法,进行应用
	for (ApplicationContextInitializer initializer : initializers) {
		initializer.initialize(context);
	}
}

4.2:ContextIdApplicationContextInitializer

用于生成application context容器的contextId,源码如下:

org.springframework.boot.context.ContextIdApplicationContextInitializer#initialize
@Override
publicvoid initialize(ConfigurableApplicationContext applicationContext) {
	// <202106081142>
	ContextId contextId = getContextId(applicationContext);
	// 设置id到applicationContext
	applicationContext.setId(contextId.getId());
	// 将contextId作为单例bean存储,以Context.class.getName()作为bean名称
	// 这样如果是我们需要获取application context的id信息的话就可以获取使用了
	applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}

<202106081142>处是生成application context容器的id,ContextId是一个用于表示容器id的内部类,如下:

org.springframework.boot.context.ContextIdApplicationContextInitializer.ContextId
static class ContextId {

	private final AtomicLong children = new AtomicLong(0);

	private final String id;

	ContextId(String id) {
		this.id = id;
	}

	ContextId createChildId() {
		return new ContextId(this.id + "-" + this.children.incrementAndGet());
	}

	String getId() {
		return this.id;
	}

}

getContextId源码如下:

org.springframework.boot.context.ContextIdApplicationContextInitializer#getContextId
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
	ApplicationContext parent = applicationContext.getParent();
	if (parent != null && parent.containsBean(ContextId.class.getName())) {
		return parent.getBean(ContextId.class).createChildId();
	}
	// 通过getApplicationId方法获取id并封装到ContextId对象中,其中getApplicationid方法源码如下
	/*
    org.springframework.boot.context.ContextIdApplicationContextInitializer#getApplicationId
	private String getApplicationId(ConfigurableEnvironment environment) {
		// 如果有属性"spring.application.name"则使用其作为id,否则使用"application"作为id
		String name = environment.getProperty("spring.application.name");
		return StringUtils.hasText(name) ? name : "application";
	}
	*/
	return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

4.3:ConfigurationWarningsApplicationContextInitializer

该类用于进行一些错误配置的检查等工作,比如我们配置了扫描spring的包org.springframework,如:
@SpringBootApplication(scanBasePackages = { "org.springframework" })则会抛出如下异常信息:
在这里插入图片描述
图中红框的信息就是ConfigurationWarningsApplicationContextInitializer生成的,打印日志执行过程如下图:
在这里插入图片描述
下面我们来具体看下,initialize源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer#initialize
@Override
public void initialize(ConfigurableApplicationContext context) {
	// <202106081408>
	context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}

<202106081408>处是注册BeanDefnitionRegistryPostProcessor,在bean定义加载完毕后,对BeanDefinitionRegistry执行一些操作,这里是检查相关。ConfigurationWarningsPostProcessor参考4.3.1:ConfigurationWarningsPostProcessor,getChecks()源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer#getChecks
// 返回需要被应用的Check
protected Check[] getChecks() {
	// 这里的check只有ComponentScanPackageCheck一个类,主要是执行@ComponentScan扫描的包路径
	// 是否合法的检查操作,Check是一个接口,如下:
	/*
	org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.Check
	// 代表一个可以被应用的检查
	@FunctionalInterface
	protected interface Check {

		// 有警告信息则返回警告,否则返回null
		String getWarning(BeanDefinitionRegistry registry);

	}
	*/
	return new Check[] { new ComponentScanPackageCheck() };
}

接下来我们看下ComponentScanPackageCheck的getWarning方法,了解具体是如何执行扫描包检查的:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck#getWarning
@Override
public String getWarning(BeanDefinitionRegistry registry) {
	// <202106081406>
	Set<String> scannedPackages = getComponentScanningPackages(registry);
	// <202106081436>
	List<String> problematicPackages = getProblematicPackages(scannedPackages);
	if (problematicPackages.isEmpty()) {
		return null;
	}
	return "Your ApplicationContext is unlikely to " + "start due to a @ComponentScan of "
			+ StringUtils.collectionToDelimitedString(problematicPackages, ", ") + ".";
}

<202106081406>处获取要扫描的包,源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck#getComponentScanningPackages
protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {
	// 返回集合
	Set<String> packages = new LinkedHashSet<>();
	// 获取所有的bean名称
	String[] names = registry.getBeanDefinitionNames();
	for (String name : names) {
		// 根据bean名称获取bean定义
		BeanDefinition definition = registry.getBeanDefinition(name);
		// 如果是有注解的bean定义
		if (definition instanceof AnnotatedBeanDefinition) {
			// 强转
			AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
			// 获取@ComponetScan注解配置的扫描包路径的值,源码如下:
			/*
			org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck#addComponentScanningPackages
			private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {
				// 获取注解ComponentScan的属性信息
				AnnotationAttributes attributes = AnnotationAttributes
						.fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));
				if (attributes != null) {
					// 获取value配置的值,添加到结果中
					addPackages(packages, attributes.getStringArray("value"));
					// 获取basePackages配置的值,添加到结果中
					addPackages(packages, attributes.getStringArray("basePackages"));
					// 获取basePackageClasses的值,添加到结果中
					addClasses(packages, attributes.getStringArray("basePackageClasses"));
					// 如果为空,则使用当前类所在的包为扫描包
					if (packages.isEmpty()) {
						packages.add(ClassUtils.getPackageName(metadata.getClassName()));
					}
				}
			}
			*/
			addComponentScanningPackages(packages, annotatedDefinition.getMetadata());
		}
	}
	return packages;
}

<202106081436>处获取有问题的扫描包路径,源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck#getProblematicPackages
private List<String> getProblematicPackages(Set<String> scannedPackages) {
	List<String> problematicPackages = new ArrayList<>();
	for (String scannedPackage : scannedPackages) {
		// <202106081438>
		// 若为有问题包路径,则添加到有问题包路径集合中
		if (isProblematicPackage(scannedPackage)) {
			problematicPackages.add(getDisplayName(scannedPackage));
		}
	}
	return problematicPackages;
}

<202106081438>处是判断是否为有问题的包路径,源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ComponentScanPackageCheck#isProblematicPackage
private boolean isProblematicPackage(String scannedPackage) {
	if (scannedPackage == null || scannedPackage.isEmpty()) {
		return true;
	}
	// 如果在PROBLEM_PACKAGES包含则为true,其信息如下:
	/*
	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包的话,这里就为true了
	return PROBLEM_PACKAGES.contains(scannedPackage);
}
4.3.1:ConfigurationWarningsPostProcessor

这是ConfigurationWarningsApplicationContextInitializer的一个内部类,实现了BeanDefinitionRegistryPostProcessor接口,关于该接口的调用时机可以参考这篇文章,就是在容器refresh时bean定义加载完毕后,通过bean定义生成spring bean之前的这个时间点,源码如下:

org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ConfigurationWarningsPostProcessor
// 用于报告警告信息
protected static final class ConfigurationWarningsPostProcessor
			implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {
	// 用于执行具体检查的数据,Check是一个接口,如下:
	/*
	org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.Check
	// 代表一个可以被应用的检查
	@FunctionalInterface
	protected interface Check {

		// 有警告信息则返回警告,否则返回null
		String getWarning(BeanDefinitionRegistry registry);

	}
	*/
	private Check[] checks;

	public ConfigurationWarningsPostProcessor(Check[] checks) {
		this.checks = checks;
	}
	
	...snip...
	
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		// 调用所有的Check对BeanDefinitionRegistry进行检查
		for (Check check : this.checks) {
			// 执行检查,获取检查结果
			String message = check.getWarning(registry);
			// 如果有警告,则调用warn方法打印警告日志
			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));
		}
	}

}

4.4:ServerPortInfoApplicationContextInitializer

用于设置WebServer实际监听的端口号到application context中,源码如下:

org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer#initialize
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
	// <202106081524>
	applicationContext.addApplicationListener(this);
}

<202106081524>处是将自己作为ApplicationListener进行注册,因为实现了ApplicationListener<WebServerInitializedEvent>,因此最终会执行其onApplicationEvent方法,如下:

org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer#onApplicationEvent
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
	// getName方法如下:
	/*
	org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer#getName
	private String getName(WebServerApplicationContext context) {
		String name = context.getServerNamespace();
		// 如果是有命名空间serverNamespace则使用,否则使用默认的server
		return StringUtils.hasText(name) ? name : "server";
	}
	*/
	// 一般结果是local.server.port
	String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
	// <202106081538>
	setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}

<202106081538>处event.getWebServer().getPort()是获取使用的端口号,该处源码如下:

org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer#setPortProperty(org.springframework.context.ApplicationContext, java.lang.String, int)
private void setPortProperty(ApplicationContext context, String propertyName, int port) {
	// 这里为true
	if (context instanceof ConfigurableApplicationContext) {
		// <202106081540>
		// 强转,调用重载版本
		setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port);
	}
	if (context.getParent() != null) {
		setPortProperty(context.getParent(), propertyName, port);
	}
}

<202106081540>处源码如下:

org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer#setPortProperty(org.springframework.core.env.ConfigurableEnvironment, java.lang.String, int)
private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) {
	// 获取封装各种来源属性信息的PropertySources
	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);
	}
	// 以local.server.port为key,以port为值,存储到server.ports属性源对应的map中
	((Map<String, Object>) source.getSource()).put(propertyName, port);
}

写在后面

参考文章

https://www.cnblogs.com/hello-shf/p/10987360.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值