当前位置: 首页 > news >正文

【Spring】SpringBoot的扩展点之ApplicationContextInitializer

简介

其实spring启动步骤中最早可以进行扩展的是实现ApplicationContextInitializer接口。来看看这个接口的注释。

package org.springframework.context;/*** 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*/
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {/*** Initialize the given application context.* @param applicationContext the application to configure*/void initialize(C applicationContext);}

简要的说明一下,有这么几点:

  1. 实现这个接口之后,它的initialize方法会在容器ConfigurableApplicationContext刷新之前触发。
  2. 它通常用于在容器初始化之前进行一些程序上的操作,比如说注册一些环境变量或者读取一些配置文件。
  3. 它可以使用@Order指定优先级

实现方式

它有三种实现方式:

  1. 通过SPI机制实现,在resources/META-INF/spring.factories中定义如下内容:
    org.springframework.context.ApplicationContextInitializer=com.alone.spring.aop.demo.config.ContextInitializerTest
/*** spring扩展点 ApplicationContextInitializer*/
@Slf4j
public class ContextInitializerTest implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {log.info("ContextInitializerTest 开始加载");ConfigurableEnvironment environment = applicationContext.getEnvironment();Map<String, Object> initMap = new HashMap<>();initMap.put("20231116", "This is init");MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);environment.getPropertySources().addLast(propertySource);log.info("ContextInitializerTest 加载结束");}
}
  1. application.yml中定义如下内容:
context:initializer:classes: com.alone.spring.aop.demo.config.YmlApplicationContextInitializer
@Slf4j
public class YmlApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {log.info("这是yml的ApplicationContextInitializer");ConfigurableEnvironment environment = applicationContext.getEnvironment();Map<String, Object> initMap = new HashMap<>();initMap.put("20231116", "YmlApplicationContextInitializer");MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);environment.getPropertySources().addLast(propertySource);log.info("YmlApplicationContextInitializer 加载结束");}
}
  1. 在启动类中进行注册:
public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);springApplication.addInitializers(new MainFlagApplicationContextInitializer());springApplication.run();
}
@Component
@Slf4j
public class MainFlagApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {log.info("这是main的ApplicationContextInitializer");ConfigurableEnvironment environment = applicationContext.getEnvironment();Map<String, Object> initMap = new HashMap<>();initMap.put("20231116", "MainFlagApplicationContextInitializer");MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);environment.getPropertySources().addLast(propertySource);log.info("MainFlagApplicationContextInitializer 加载结束");}
}

三者的加载顺序是:
application.yml >spring.factories >启动类
在这里插入图片描述

源码分析

从启动类的new SpringApplication(SpringbootApplication.class)开始分析:

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));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}

看到上面第8行(源码266行)中出现了ApplicationContextInitializer.class猜想它肯定是在读取相关的配置,跟进去发现出现了下面这行。

在这里插入图片描述

这里是读取了spring.factories中的内容,但看它的结果发现不止我们自定义的类一个,说明springboot内置了一些ApplicationContextInitializer,后续我们再看它们具体的作用,这里先截图列出按下不表。

在这里插入图片描述

然后沿如下的调用栈可以找到initializer.initialize(context);这一行调用ApplicationContextInitializer的语句。
springApplication.run()
run:306, SpringApplication (org.springframework.boot)
prepareContext:383, SpringApplication (org.springframework.boot)
applyInitializers:614, SpringApplication (org.springframework.boot)

框起来的方法会对所有的initializer进行排序,排序后的结果见左边。
在执行到DelegatingApplicationContextInitializer时会去读取环境中的context.initializer.classes,也就是application.yml中配置的内容执行。所以会先执行yml配置的initializer.
在这里插入图片描述

以上总结一下是这样的:
在这里插入图片描述

大致调用的流程图是:
在这里插入图片描述

系统内置初始化类

最后我们来看看上面提到的系统内置的初始化类都有些什么作用。

SharedMetadataReaderFactoryContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);applicationContext.addBeanFactoryPostProcessor(postProcessor);
}

初始化了一个CachingMetadataReaderFactoryPostProcessor至容器中

DelegatingApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext context) {ConfigurableEnvironment environment = context.getEnvironment();List<Class<?>> initializerClasses = getInitializerClasses(environment);if (!initializerClasses.isEmpty()) {applyInitializerClasses(context, initializerClasses);}
}

执行context.initializer.classes配置的initializer。

ContextIdApplicationContextInitializer

@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) {ApplicationContext parent = applicationContext.getParent();if (parent != null && parent.containsBean(ContextId.class.getName())) {return parent.getBean(ContextId.class).createChildId();}return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}private String getApplicationId(ConfigurableEnvironment environment) {String name = environment.getProperty("spring.application.name");return StringUtils.hasText(name) ? name : "application";
}

设置容器的id,值取自spring.application.name配置,默认是application

ConditionEvaluationReportLoggingListener

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {this.applicationContext = applicationContext;applicationContext.addApplicationListener(new ConditionEvaluationReportListener());if (applicationContext instanceof GenericApplicationContext) {// Get the report early in case the context fails to loadthis.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());}
}

注册了一个ConditionEvaluationReportListener

RestartScopeInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
}

自动重启相关。

ConfigurationWarningsApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext context) {context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}

初始化一个ConfigurationWarningsPostProcessor用于记录公共的容器配置错误信息。

RSocketPortInfoApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.addApplicationListener(new Listener(applicationContext));
}

增加了一个监听器用于监听RSockerServer的端口是否正常。
在这里插入图片描述

ServerPortInfoApplicationContextInitializer

/*** {@link ApplicationContextInitializer} that sets {@link Environment} properties for the* ports that {@link WebServer} servers are actually listening on. The property* {@literal "local.server.port"} can be injected directly into tests using* {@link Value @Value} or obtained via the {@link Environment}.* <p>* If the {@link WebServerInitializedEvent} has a* {@link WebServerApplicationContext#getServerNamespace() server namespace} , it will be* used to construct the property name. For example, the "management" actuator context* will have the property name {@literal "local.management.port"}.* <p>* Properties are automatically propagated up to any parent context.** @author Dave Syer* @author Phillip Webb* @since 2.0.0*/@Override
public void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.addApplicationListener(this);
}

向容器中增加一个监听器用于检测WebServer的端口是否正常监听。

参考资料

  1. SpringBoot系统初始化器使用及源码解析(ApplicationContextInitializer)
  2. 跟我一起阅读SpringBoot源码(九)——初始化执行器
  3. Springboot扩展点之ApplicationContextInitializer
http://www.lryc.cn/news/237880.html

相关文章:

  • 哈希表HashTable
  • 【软件测试】一位优秀测试工程师具备哪些知识和经验?
  • MongoDB相关基础操作(库、集合、文档)
  • 进程和线程( Process and Thread)
  • linux apache安装及虚拟主机配置
  • 基于Spring Boot 框架的试卷自动生成系统的设计与实现
  • 开发《猫咪攻略》小游戏的意义
  • hadoop、hive、DBeaver的环境搭建及使用
  • Linux上通过SSL/TLS和start tls连接到LDAP服务器(附C++代码实现认证流程)
  • HarmonyOS ArkTS List组件和Grid组件的使用(五)
  • 考研思想政治理论大纲
  • 日期格式转化成星期几部署到linux显示英文
  • 一个关于proto 文件的经验分享 :gRPC 跨语言双端通信显示错误码:12 UNIMPLEMENTED (附赠gRPC错误码表)
  • 腾讯极光盒子A4021增强版_线刷官方
  • 机器学习第11天:降维
  • 异步爬取+多线程+redis构建一个运转丝滑且免费http-ip代理池 (三)
  • VSCode新建Vue项目
  • 前端学习--React(1)
  • HarmonyOS从基础到实战-高性能华为在线答题元服务
  • OpenCV快速入门:窗口交互
  • 数据智能引擎:企业模糊搜索API精准获取企业列表信息
  • 汇编-间接寻址(处理数组)
  • lombok 的使用讲解
  • echarts的使用
  • js进阶笔记之构造函数
  • Codesys数据类型(2.7):扩展数据类型之 别名 详解
  • 白盒子测试总结
  • 字符数组基础知识
  • Oracle EBS 重新打开库存会期间
  • java项目之社区互助平台(ssm+vue)