初始化器解析
系统初始化器介绍
先看看接口的注释
/**
* 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
*/
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
简单来说:Spring容器刷新之前执行的一个回调函数,用于向SpringBoot容器中注册属性,可以用Order接口排序
容器初始化器的使用方式
三种使用它的方式,先写个简单的例子
编写三个Initializer,分别用三种不同的方式注册
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run firstInitializer");
}
}
@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
HashMap<String, Object> map = new HashMap<>();
map.put("key2", "value2");
MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run secondInitializer");
}
}
@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
HashMap<String, Object> map = new HashMap<>();
map.put("key3", "value3");
MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
environment.getPropertySources().addLast(mapPropertySource);
System.out.println("run thirdInitializer");
}
}
1、在/META-INF/spring.facotries下
org.springframework.context.ApplicationContextInitializer=top.luyuni.sb2.initializer.FirstInitializer
2、SpringApplication实例化的时候硬编码
@SpringBootApplication
@MapperScan("top.luyuni.sb2.mapper")
public class Sb2Application {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Sb2Application.class);
springApplication.addInitializers(new SecondInitializer());
springApplication.run(args);
}
}
3、在application.properties中配置
context.initializer.classes=top.luyuni.sb2.initializer.ThirdInitializer
为了测试,编写service和controller
@Service
public class TestService implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public String test(Long id){
return applicationContext.getEnvironment().getProperty("key" + id.toString());
}
}
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/testInitializer/{id}")
public String testInitializer(@PathVariable("id") Long id){
return testService.test(id);
}
}
启动工程
看看控制台输出
咦?怎么回事和Order中指定的不一样?这个等我们后面分析
打开浏览器测试一个环境属性有没有设置进去
看起来没有什么问题。使用我们就讲完了
来看看我们是怎么加载系统初始化器的
第一种方式,配置spring.factores,使用SpringFactoriesLoader
从SpringApplication.run中一步步点进去
org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
org.springframework.boot.SpringApplication#SpringApplication(java.lang.Class<?>...)
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.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();
// 就在这里这是关键方法
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
点到这里我们就发现,SpringApplication初始化的时候,会使用SpringFactoriesLoader来加载ApplicationContextInitializer
来我们来看看SpringFactoriesLoader是怎么加载的,点进去看看
org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
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 ?
// SpringFactoriesLoader#FACTORIES_RESOURCE_LOCATION
// = "META-INF/spring.factories"
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);
}
}
看到这里我们知道了SpringFactoriesLoader他会读取/META-INF/spring.factories的配置
读取到配置后我们回到org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, java.lang.Class<?>[], java.lang.Object...)里的org.springframework.boot.SpringApplication#createSpringFactoriesInstances进去看一看
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 实例化系统初始化器是在这里做的啊
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
发现它会在这里实例化系统初始化器
然后又回去看看org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class, java.lang.Class<?>[], java.lang.Object...)看到这个方法就是给系统初始化器拍一个序ok了~
org.springframework.core.annotation.AnnotationAwareOrderComparator#sort(java.util.List<?>)
拿到排过序的Initializer后就把其放在
org.springframework.boot.SpringApplication#setInitializers
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
至此org.springframework.boot.SpringApplication#initializers完成赋值
第二种,直接硬编码设置
org.springframework.boot.SpringApplication#addInitializers
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
这太好理解了,就是直接把org.springframework.boot.SpringApplication#initializers硬编码赋值
第三种方式,在application.properties中配置
这个的话我们要关注org.springframework.boot.context.config.DelegatingApplicationContextInitializer这个类
分析它的org.springframework.boot.context.config.DelegatingApplicationContextInitializer#initialize
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
点进来org.springframework.boot.context.config.DelegatingApplicationContextInitializer#getInitializerClasses
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
// 我们要到它这里会从环境变量中读取配置
//DelegatingApplicationContextInitializer#PROPERTY_NAME
// = "context.initializer.classes"
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;
}
所以它是通过DelegatingApplicationContextInitializer的初始化方法找到的
其他的初始化逻辑和SpringFactoriesLoader的方式相似,这里感兴趣可以自己点进去看看
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;
我们还可以看到这个初始化器的order=0所以它最先加载,这也是为什么thridInitializer最先打印的原因了
关于系统初始化器的执行时机源码解析
一步步点进去
org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
// .....
// 找到了在这里,点进去
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 这个是传说中的刷新上下文方法
refreshContext(context);
afterRefresh(context, applicationArguments);
// ...
return context;
}
org.springframework.boot.SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 找到了在这里,点进去
applyInitializers(context);
// ...
}
org.springframework.boot.SpringApplication#applyInitializers
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);
}
}
大致调用流程图