[Ngbatis源码学习][SpringBoot] ApplicationContextInitializer接口类的使用和原理解读
ApplicationContextInitializer接口类的使用和原理解读
在看Ngbatis源码的过程中,看到了自定义的ApplicationContextInitializer实现类,对ApplicationContextInitializer接口不是特别的理解,所以趁此机会总结下对其的理解和使用。
1. 作用
- ApplicationContextInitializer(系统初始化器),在 Spring 容器化开始的时候,ApplicationContextInitializer接口的所有实现在类被实例化。
- 在Spring容器刷新前,所有实现类的
org.springframework.context.ApplicationContextInitializer#initialize
方法会被调用,initialize 方法的形参类型是 ConfigurableApplicationContext,因此可以认为 ApplicationContextInitializer 实际上是Spring容器初始化前 ConfigurableApplicationContext 的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
2. 加载方式
通过具体的实现类实现ApplicationContextInitializer接口。
Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:
- 实现ApplicationContextInitializer接口
- 把实现类注册到Spring容器中
其中把实现了ApplicationContextInitializer接口的实现类注册到Spring容器中,主要有三种方式:
- spring.factories
- 启动类中配置
- application.properties
以下一一介绍。
2.1. spring.factories
springboot会扫描所有jar包下的 META-INF/spring.factorties
,比如预置以下内容,即可完成自定义MyApplicationContextInitializer的注册:
org.springframework.context.ApplicationContextInitializer=com.knqiufan.config.MyApplicationContextInitializer
2.2. 启动类中配置
在SpringBoot的启动类中,使用SpringApplication.addInitializers(new MyApplicationContextInitializer())完成自定义MyApplicationContextInitializer的注册:
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(TestApplication.class);
springApplication.addInitializers(new MyApplicationContextInitializer());
springApplication.run(args);
}
}
2.3. application.properties
在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册:
context.initializer.classes=com.knqiufan.config.MyApplicationContextInitializer
3. 初始化时机
ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中。
即Spring容器初始化开始前,先通过 org.springframework.boot.SpringApplication#getSpringFactoriesInstances
得到所有实现类的集合,然后通过 org.springframework.boot.SpringApplication#setInitializers
注入到SpringApplication类的initializers属性中:
/**
* SpringApplication构造函数
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 获得所有实现类的集合
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 给属性 List<ApplicationContextInitializer<?>> initializers 赋值
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
org.springframework.boot.SpringApplication#getSpringFactoriesInstances
方法源码如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 使用Set保证名称唯一,以防止重复
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以看到调用的是 SpringFactoriesLoader.loadFactoryNames 静态方法来加载所有 ApplicationContextInitializer 的实现类名称。
SpringFactoriesLoader.loadFactoryNames
中调用了 loadSpringFactories 方法,在 loadSpringFactories 方法中就可以看到加载了 META-INF/spring.factories
文件,并将文件中的实现类加入到缓存中。源码如下:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
// 获取 META-INF/spring.factories 下配置的所有实现类
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
// ... 省略部分代码 ...
// 加载的ApplicationContextInitializer实现类加入缓存
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
4. 执行时机
ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext
-->org.springframework.boot.SpringApplication#applyInitializers
中,也就是Spring容器正式刷新前,准备上下文环境时。
/**
* Spring容器准备上下文环境方法
*/
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 执行 ApplicationContextInitializer 实现类的 initialize() 方法
applyInitializers(context);
//... 省略其他代码 ...
}
/**
* 在此方法中执行 ApplicationContextInitializer 实现类中的 initialize() 方法
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
// 获取所有实现类,并进行遍历
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
// 执行实现类的initialize方法
initializer.initialize(context);
}
}
浅浅看下 org.springframework.boot.SpringApplication#getInitializers
方法。
在前面初始化时,调用 org.springframework.boot.SpringApplication#setInitializers
方法将获取到的所有的 ApplicationContextInitializer 接口实现类加入到 initializers 属性中,在这里的 org.springframework.boot.SpringApplication#getInitializers
方法就负责将 initializers 属性中数据拿出来。
源码如下:
/**
* 返回使用 Order 排序过的 ApplicationContextInitializer 实现类的Set列表
* Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that
* will be applied to the Spring {@link ApplicationContext}.
* @return the initializers
*/
public Set<ApplicationContextInitializer<?>> getInitializers() {
return asUnmodifiableOrderedSet(this.initializers);
}
private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
List<E> list = new ArrayList<>(elements);
list.sort(AnnotationAwareOrderComparator.INSTANCE);
return new LinkedHashSet<>(list);
}
5. 内置实现类
Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现。简单介绍几个:
DelegatingApplicationContextInitializer
ContextIdApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer
ServerPortInfoApplicationContextInitializer
5.1. DelegatingApplicationContextInitializer
ApplicationContextInitializer 其中一个的实现方式就是在 application.properties 配置文件中对实现类进行配置。
DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法。
源码很好理解,如下:
/**
* {@link ApplicationContextInitializer} that delegates to other initializers that are
* specified under a {@literal context.initializer.classes} environment property.
* 获取 context.initializer.classes 这个环境变量中配置的实现类,并执行
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.0.0
*/
public class DelegatingApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
// NOTE: Similar to org.springframework.web.context.ContextLoader
// 定义的环境变量名称
private static final String PROPERTY_NAME = "context.initializer.classes";
private int order = 0;
@Override
public void initialize(ConfigurableApplicationContext context) {
// 根据配置上下文获取配置信息
ConfigurableEnvironment environment = context.getEnvironment();
// 获取所有配置的实现类
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
// 执行实现类的 initialize 方法
applyInitializerClasses(context, initializerClasses);
}
}
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
// 获取 PROPERTY_NAME 环境变量名配置的所有实现类类名
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);
}
}
/**
* 执行实现类的 initialize 方法
*/
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
Class<?> contextClass = context.getClass();
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
for (Class<?> initializerClass : initializerClasses) {
initializers.add(instantiateInitializer(contextClass, initializerClass));
}
// 执行实现类的 initialize 方法
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()));
return (ApplicationContextInitializer<?>) BeanUtils.instantiateClass(initializerClass);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void applyInitializers(ConfigurableApplicationContext context,
List<ApplicationContextInitializer<?>> initializers) {
initializers.sort(new AnnotationAwareOrderComparator());
// 遍历所有 ApplicationContextInitializer 接口的实现类,执行实现类的 initialize 方法
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
5.2. ContextIdApplicationContextInitializer
ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为 “application”。
以下只展示重点源码,有兴趣可以直接去查看完整代码:
public class ContextIdApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
// ... 省略部分代码 ...
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
// ... 省略部分代码 ...
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}
// 设置 Spring 应用上下文 ID,若没设置则默认为 “application”
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.getProperty("spring.application.name");
return StringUtils.hasText(name) ? name : "application";
}
// ... 省略部分代码 ...
}
5.3. ConfigurationWarningsApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置。
该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
以下只展示重点源码,有兴趣可以直接去查看完整代码:
public class ConfigurationWarningsApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);
/**
* 增加了一个 Bean 的后置处理器,在注册 BeanDefinition 实例之后生效。
* 用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
*/
@Override
public void initialize(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}
/**
* Returns the checks that should be applied.
* @return the checks to apply
*/
protected Check[] getChecks() {
return new Check[] { new ComponentScanPackageCheck() };
}
/**
* {@link BeanDefinitionRegistryPostProcessor} to report warnings.
*/
protected static final class ConfigurationWarningsPostProcessor
implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {
// ... 省略部分代码 ...
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
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));
}
}
}
// ... 省略部分代码 ...
}
5.4. ServerPortInfoApplicationContextInitializer
ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中。
public class ServerPortInfoApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, ApplicationListener<WebServerInitializedEvent> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 注册为监听器
applicationContext.addApplicationListener(this);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
// ... 省略部分代码 ...
}
6. 总结
- 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法都会被调用,所以可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作。
- ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的
- 深入理解了ApplicationContextInitializer接口实现类的初始化时机和执行时机
- 了解了一些内部实现类的作用和实现方法,可以学习到Spring本身是如何利用扩展接口实现一些功能,在实际的项目开发中具有一定的参考意义。
本文来自博客园,作者:knqiufan,转载请注明原文链接:https://www.cnblogs.com/knqiufan/p/17855122.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本