1. 什么是系统初始化器
系统初始化器是一种回调机制,可以让我们扩展自定义的属性。
2. 如何自定义系统初始化器SpringBoot提供了三种自定义系统初始化器的方案,接下来我们一一进行实现。
ApplicationContextInitializer
要实现自定义系统的初始化器必须实现ApplicationContextInitializer接口,并且可以使用@Order注解进行排序,下面我来创建三个初始化器。
@Order(1) public class FirstInitializer implements ApplicationContextInitializer{ @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map 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{ @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map 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{ @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); Map 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 ,就是我们之前系统初始化器中设置的变量。
3. SpringBoot系统初始化器实现原理 3.1 对类初始化器进行初始化并放入到 cache 中注:如果使用 junit 进行单元测试,使用 SpringApplication 的 addInitializer 方法添加的系统初始化器时不生效的。
在 SpringApplication 的构造器中会去初始化系统初始化器,通过 SpringFactoriesLoader.loadFactoryNames 来进行初始化
privateCollection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List 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 List3.2 执行系统初始化器loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map > loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration 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); } }
在 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, 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 > initializerClasses = getInitializerClasses(environment); if (!initializerClasses.isEmpty()) { applyInitializerClasses(context, initializerClasses); } } private List > getInitializerClasses(ConfigurableEnvironment env) { String classNames = env.getProperty(PROPERTY_NAME); List > 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 > initializerClasses) { Class> contextClass = context.getClass(); List > 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 > 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; }
以上就是类初始化器的使用和原理,大家有什么问题欢迎在评论区进行讨论。



