深入理解SpringBoot之系统初始化器
1. 什么是系统初始化器
系统初始化器是一种回调机制,可以让我们扩展自定义的属性。
2. 如何自定义系统初始化器
SpringBoot提供了三种自定义系统初始化器的方案,接下来我们一一进行实现。
ApplicationContextInitializer<ConfigurableApplicationContext>
要实现自定义系统的初始化器必须实现ApplicationContextInitializer接口,并且可以使用@Order注解进行排序,下面我来创建三个初始化器。
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String,Object> map = new HashMap<>();
map.put("key1","value1");
MapPropertySource propertySource = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addFirst(propertySource);
System.out.println("run firstInitializer");
}
}
@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String,Object> map = new HashMap<>();
map.put("key2","value2");
MapPropertySource propertySource = new MapPropertySource("secondInitializer", map);
environment.getPropertySources().addFirst(propertySource);
System.out.println("run secondInitializer");
}
}
@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String,Object> map = new HashMap<>();
map.put("key3","value3");
MapPropertySource propertySource = new MapPropertySource("thirdInitializer", map);
environment.getPropertySources().addFirst(propertySource);
System.out.println("run thirdInitializer");
}
}
启动类实现:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
第一种实现方式:在 classpath 下创建 META-INF 文件,并在下面创建 spring.factories 文件
配置文件内容如下
org.springframework.context.ApplicationContextInitializer=com.wys.initializer.FirstInitializer
这样我们就配置好了第一个系统初始化器,接下来运行,可以看到第一个系统初始化器已经生效。
配置第二个系统初始化器,修改启动类并运行
public static void main(String[] args) {
//SpringApplication.run(Application.class,args);
SpringApplication application = new SpringApplication(Application.class);
application.addInitializers(new SecondInitializer());
application.run(args);
}
可以看到第二个系统初始化器也成功的运行了,接下来配置第三个初始化器,在 application.properties 中添加如下配置并运行
# 系统初始化器配置
context.initializer.classes=com.wys.initializer.ThirdInitializer
可以看到三个系统初始化器都已经成功加载了,但是可以看到 ThirdInitializer 这个初始化器的 order 是最小的,在这里是第一个进行初始化的,后面我们讲原理时会给出解释。
接下来我们可以调用系统初始化器中初始化的变量
@Service
public class DemoService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public String test(){
return this.applicationContext.getEnvironment().getProperty("key1");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class DemoTest {
@Autowired
private DemoService demoService;
@Test
public void test1(){
System.out.println(this.demoService.test());
}
}
运行上面测试程序可以看到输出结果为 value1 ,就是我们之前系统初始化器中设置的变量。
注:如果使用 junit 进行单元测试,使用 SpringApplication 的 addInitializer 方法添加的系统初始化器时不生效的。
3. SpringBoot系统初始化器实现原理
3.1 对类初始化器进行初始化并放入到 cache 中
在 SpringApplication 的构造器中会去初始化系统初始化器,通过 SpringFactoriesLoader.loadFactoryNames 来进行初始化
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<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在这里可以看到使用 classLoader.getResource 来获取资源,FACTORIES_RESOURCE_LOCATION 值为 META-INF/spring.factories。
会将所有jar包下面的这个配置转换为 properties 并放入 cache 中,第二次获取就直接读 cache 中的数据。
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 {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
3.2 执行系统初始化器
在 run 方法中该代码中 prepareContext 方法中会调用 applyInitializers 方法来执行类的初始化器,applyInitializer会便利系统初始化器并执行 initializer 方法。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
applyInitializers(context);
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.");
initializer.initialize(context);
}
}
3.3 为什么 ThirdInitializer 的 @Order注解会失效?
SpringBoot 会对初始化器进行排序,可以看到在我们自定义的初始化器前面存在两个初始化器,他们的 Order 都为 0,具备更高的优先级。
在 DelegatingApplicationContextInitializer 这个类中的 initializer 方法会寻找配置文件中 context.initializer.classes 配置的初始化器并执行,我把代码贴过来大家一看便知。
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()) {
applyInitializerClasses(context, initializerClasses);
}
}
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;
}
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);
}
}
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));
}
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());
for (ApplicationContextInitializer initializer : initializers) {
initializer.initialize(context);
}
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
以上就是类初始化器的使用和原理,大家有什么问题欢迎在评论区进行讨论。