系统初始化器
简介:ApplicationContextInitializer
是Spring框架提供的一个接口,用于在ApplicationContext
(应用上下文)被创建之前对其进行初始化或配置,实现了该接口的类可以在Spring应用程序启动时被调用 ,在Spring Boot中,ApplicationContextInitializer
常用于执行一些与应用程序配置和环境相关的初始化操作。
三种使用方式
首先,需要实现ApplicationContextInitializer
这个类
java
@Order(0)
public class MyInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("key1", "value1");
MapPropertySource mapPropertySource = new MapPropertySource("name", hashMap);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("Initializer init");
}
}
实现完成后可以通过三种不同的方式加载
- META-INF/spring.factories 文件配置
在 src/main/resources
目录下创建 META-INF/spring.factories
文件,并在该文件中指定 ApplicationContextInitializer
的实现类。Spring Boot 在启动时会自动加载该文件,并执行相应的初始化操作。
ini
org.springframework.context.ApplicationContextInitializer=org.springframework.context.ApplicationContextInitializer=com.huakai.initializer.MyInitializer
- 在
SpringApplication.run()
方法中指定ApplicationContextInitializer
实现类
arduino
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.addInitializers(new MyInitializer());
application.run(args);
}
- 在
application.properties
配置
在配置文件中,通过配置 context.initializer.classes
属性来指定要执行的 ApplicationContextInitializer
实现类
ini
context.initializer.classes=com.huakai.initializer.MyInitializer
实现接口时我们可以通过@Order(0)
来指定容器的加载顺序,但是通过配置文件(application.properties
)来指定实现类的这种方式,Spring
会按照配置的顺序(逗号分割)来加载这些初始化器,它的优先级是高于其他方式注册的系统自定义实现的
环境变量获取
通过初始化器配置的环境变量可以通过ApplicationContext
类获取
kotlin
@Controller
@RequestMapping("/api/v1/init")
public class InitTestController {
private final ApplicationContext application;
public InitTestController(ApplicationContext application) {
this.application = application;
}
@ResponseBody
@GetMapping("/first")
public String getFirstInitializer() {
return application.getEnvironment().getProperty("key1");
}
}
访问本地项目接口地址即可获取到对应的值
调用流程
run() -> prepareContext() 准备上下文 -> applyInitializers() 调用系统初始化器-> 遍历调用初始化器
SpringFactoriesLoader
SpringFactoriesLoader
是 Spring
框架提供的一个工具类,用于加载并解析 META-INF/spring.factories
文件中的配置信息。我们可以将自己的实现注册到 spring.factories
文件中,Spring
在启动时会使用 SpringFactoriesLoader
加载这些配置信息,从而实现自动化的配置和装配。
首先来看下SpringApplication
的构造方法
kotlin
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@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();
// 设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
注意注释里的这句话The instance can be customized before calling {@link #run(String...)}
调用run
方法之前可以自定义实例
typescript
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 在META-INF/spring.factories 文件中查找对应类型的工厂类的配置
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names(加载工厂名字)创建工厂实例并返回
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
getSpringFactoriesInstances
这个方法用于加载 Spring
配置中定义的各种工厂类的实例,其中就包括了加载自动配置类,如何加载呢?SpringFactoriesLoader
终于来了
typescript
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @see #loadFactories
* @throws IllegalArgumentException if an error occurs while loading factory names
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 获取缓存
MultiValueMap<String, String> result = cache.get(classLoader);
// 存在则直接返回
if (result != null) {
return result;
}
try {
// 从spring.factories中获取配置资源
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 遍历资源存入Properties对象
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍历Properties
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 将属性值解析为逗号分隔的字符串数组放入集合
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
// 存入result,key是属性的键
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
// 存入缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这段代码的具体流程如下:
-
首先从缓存中获取已经加载的工厂类配置信息,如果已经存在,则直接返回
-
如果缓存中不存在,则尝试从指定的类加载器中获取配置文件
META-INF/spring.factories
的URL资源 -
遍历所有找到的URL资源,对每个资源执行以下操作:
-
创建一个
UrlResource
对象,表示该资源的URL -
使用
PropertiesLoaderUtils
加载资源中的属性信息,将其存储在Properties
对象中 -
遍历属性集合,对每个属性进行处理:
- 将属性值解析为逗号分隔的字符串数组,表示多个工厂类的类名
- 将解析后的工厂类名列表添加到结果集中,键为属性的键
-
-
将加载到的工厂类配置信息存入缓存
-
如果在加载过程中发生I/O异常,则抛出
IllegalArgumentException
异常,提示无法从指定位置加载工厂类信息