自定义初始化器初始化器
在 Springboot 中使用自定义初始化器大致可以分为以下两个步骤:
- 自定义初始化器,一般是实现 ApplicationContextInitializer 接口。
- 注册初始化器。
为何要自定义初始化器
Spring 是一个扩展性很强的容器框架,为开发者提供了丰富的扩展入口,其中一个扩展点便是 ApplicationContextInitializer (应用上下文初始化器 )。
ApplicationContextInitializer 是 Spring 在执行 ConfigurableApplicationContext.refresh() 方法对应用上下文进行刷新之前调用的一个回调接口,用来完成对 Spring 应用上下文个性化的初始化工作,该接口定义在 org.springframework.context 包中,其内部仅包含一个 initialize() 方法,其定义代码如下。
package org.springframework.context;
/**
* Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
* prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
*/
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
Springboot定义的 ApplicationContextInitializer 接口的实现类有下面几个
-
DelegatingApplicationContextInitializer
DelegatingApplicationContextInitializer 初始化器负责读取核心配置文件 context.initializer.classes 配置项指定的初始化器,并调用它们的 initialize() 方法来完成对应用上下文的初始化工作。
-
ContextIdApplicationContextInitializer
ContextIdApplicationContextInitializer 初始化器的作用是给应用上下文设置一个ID,这个ID由 name 和 index 两个部分组成。 其中 name 会依次从读取核心配置的以下三个属性,如果存在则立即使用,若不存在则继续查找下一个,都不存在则取默认值 “application”。 spring.application.name vcap.application.name spring.config.name index 会依次从核心配置的以下几个属性获取,如果存在则立即使用,若不存在则继续查找下一个,都不存在则为空。 vcap.application.instance_index spring.application.index server.port PORT 所以,ID的格式应为 [application.name][:application.index] ,例如,application:8080 。
-
ConfigurationWarningsApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer 初始化器用来对常见的由于配置错误而引起的警告进行打印报告
ServerPortInfoApplicationContextInitializer 初始化器通过监听 EmbeddedServletContainerInitializedEvent 事件,来对内部服务器实际要监听的端口号进行属性设置。
SharedMetadataReaderFactoryContextInitializer 初始化器用来创建一个可以在 ConfigurationClassPostProcessor 和Spring Boot 之间共享的CachingMetadataReaderFactory。
AutoConfigurationReportLoggingInitializer 初始化器用来将 ConditionEvaluationReport 记录的条件评估详情输出到日志,默认使用 DEBUG 级别,当有异常问题发生时会使用 INFO 级别。
各个初始化器的执行顺序如下:
DelegatingApplicationContextInitializer -->
ContextIdApplicationContextInitializer --> ConfigurationWarningsApplicationContextInitializer -->ServerPortInfoApplicationContextInitializer–>SharedMetadataReaderFactoryContextInitializer–> AutoConfigurationReportLoggingInitializer
实战
第一种创建方式Initializer的方式
public class MyFirstApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
Map<String,Object> mps = new HashMap<>();
mps.put("key1","zhangsan");
MapPropertySource mapPropertySource = new MapPropertySource("first",mps);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run firstInitializer");
}
2.创建META-INF和spring.factories
org.springframework.context.ApplicationContextInitializer=com.zhang.initailizer.MyFirstApplicationContextInitializer
3.创建一个service文件夹
当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。
public class Testservice implements ApplicationContextAware{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public String test(){
return applicationContext.getEnvironment().getProperty("key1");
}
}
4.创建一个controller文件夹
@RestController
public class TestController {
@Autowired
private Testservice testservice;
@RequestMapping("/test")
public String test(){
return testservice.test();
}
}
http://localhost:8888/test
第一种启动方式
通过在CLASSPATH/META-INF/spring.factories中添加 org.springframework.context.ApplicationContextInitializer 配置项进行注册。
- 实现ApplicationContextInitializer接口
- spring.factories 添加接口实现
- key值为org.springframework.context.ApplicationContextInitializer
第二种启动方式
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//SpringApplication.run(Application.class, args);
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.addInitializers(new MySecondApplicationContextInitializer());
ConfigurableApplicationContext context = springApplication.run(args);
}
}
- 实现ApplicationContextInitializer接口
- springApplication类初始化设置进去
第三种启动方式
在 Springboot 核心配置文件 application.properties 中增加 context.initializer.classes = [ 初始化器全类名 ] 进行注册。
- 实现ApplicationContextInitializer接口
- 在application.properties填写接口实现
- key值为context.initializer.classes
三种方式实现ApplicationContextInitializer接口 ,order值越小越先执行,不过 application.properties定义优先于其他方法。
Spring Boot 关于初始化器代码逻辑梳理:
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.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
// 设置将应用到Spring ApplicationContext的ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置将应用到SpringApplication并注册到ApplicationContext的applicationlistener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
进入getSpringFactoriesInstances(ApplicationContextInitializer.class)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
// 使用Set集合确保唯一,以防止重复
// SpringFactoriesLoader.loadFactoryNames(type, classLoader) 从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名
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(type, classLoader)
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
进入loadSpringFactories(classLoaderToUse)
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 判断加载器是否存在对应的缓存,存在直接返回
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// FACTORIES_RESOURCE_LOCATION=META-INF/spring.factories
// classLoader.getResources会将所有jar保重classpath下的META-INF/spring.factories中的路径都获取到
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据URL获取到对应资源的位置
UrlResource resource = new UrlResource(url);
// 加载并解析对应的spring.factories文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// 将所有列表替换为包含唯一元素的不可修改列表
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}