官方文档
/**
* 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> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
ApplicationContextInitializer 是Spring容器刷新之前执行的一个回调函数,主要用于向SpringBoot容器中注册属性。
系统初始化器的实现
实现方式一
1.实现ApplicationContextInitializer接口
2.在/META-INF/spring.facotries 填写接口的实现,key值为org.springframework.context.ApplicationContextInitializer
实现方式二
1.实现ApplicationContextInitializer接口
2.SpringApplication实例化的时候硬编码
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(TestApplication.class);
springApplication.addInitializers(new TestSInitializer());
springApplication.run(args);
}
实现方式三
1.实现ApplicationContextInitializer接口
2.在application.properties中配置,key值为context.initializer.classes
(系统初始化器可通过Order接口排序,但是第三种比前面两种先执行)
SpringFactoriesLoader介绍
/**
* General purpose factory loading mechanism for internal use within the framework.
*
* <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
* factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
* may be present in multiple JAR files in the classpath. The {@code spring.factories}
* file must be in {@link Properties} format, where the key is the fully qualified
* name of the interface or abstract class, and the value is a comma-separated list of
* implementation class names. For example:
*
* <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
*
* where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
* and {@code MyServiceImpl2} are two implementations.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,与 java spi 类似,只需要在模块的 META-INF/spring.factories 文件中,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中。
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构造过程主要包含
1)推断当前应用类型
2)设置ApplicationContext初始化器、Application监听器
3)根据堆栈来推断当前main方法所在的主类
这里主要分析这一行代码
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
进入getSpringFactoriesInstances方法(间接进入)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//重点:调用了SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法
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)方法 (这个方法最主要的是调用loadSpringFactories()这个方法,下面直接分析这个方法)
/**
* 使用指定的classloader扫描classpath上所有的JAR包中的文件META-INF/spring.factories,加载其中的多值
* 工厂属性定义,使用多值Map的形式返回
**/
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//查询缓存
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 扫描classpath上所有JAR中的文件META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 找到的每个META-INF/spring.factories文件都是一个Properties文件,将其内容
// 加载到一个 Properties 对象然后处理其中的每个属性
URL url = urls.nextElement();
// url 对应某个 META-INF/spring.factories 配置文件资源
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// properties 来自 url 对应某个 META-INF/spring.factories 配置文件资源
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取工厂类名称(接口或者抽象类的全限定名)
String factoryClassName = ((String) entry.getKey()).trim();
// 将逗号分割的属性值逐个取出,然后放到多值Map结果result中去。
for (String factoryName : StringUtils.commaDelimitedListToStringArray(
(String) entry.getValue())) {
// 放到 result 中 :
// key 使用 factoryClassName
// value 可能有多值, 使用 factoryName
result.add(factoryClassName, factoryName.trim());
}
}
}
// 放到缓存中,key 使用 classLoader
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
通过loadSpringFactories()获得所有的工厂属性,然后通过getOrDefault()过滤获得key为
org.springframework.context.ApplicationContextInitializer的值并返回。然后通过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;
}
系统初始化器的调用
由官方文档可知,ApplicationContextInitializer接口是用于ConfigurableApplicationContext通过调用refresh函数来初始化Spring容器之前的回调函数;通过断点调试可知(这里不做详细的讲解直接跳过进入主要方法),进入prepareContext(),prepareContext()里面调用了applyInitializers(context);
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
//判断initializer的泛型是不是ConfigurableApplicationContext,如果是调用initializer的initialize方法。
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
对于第一和第二种系统初始化器的实现方式调用大致原理相同,第二种系统初始化器是在ApplicationContext构建完成之后通过硬编码的方式添加的,调用过程相同。下面分析第三种。
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;
}
}
第三种实现方式在DelegatingApplicationContextInitializer中实现加载,我们可以看到DelegatingApplicationContextInitializer的initialize,从容器上下文中的Environment中获取配置名为context.initializer.classes的属性值(多个用,隔开,实际配置的是类全名),如果存在就通过反射实例化这些ApplicationContextInitialzier对象并排序,遍历并调用其initialize方法。因为DelegatingApplicationContextInitializer的Order为0,所以方式三
application.properties中配置的执行顺序先于前面两种。