[SpringBoot] ApplicationContextInitializer接口类的使用和原理解读

发布时间 2023-11-25 02:50:46作者: knqiufan

ApplicationContextInitializer接口类的使用和原理解读

在看Ngbatis源码的过程中,看到了自定义的ApplicationContextInitializer实现类,对ApplicationContextInitializer接口不是特别的理解,所以趁此机会总结下对其的理解和使用。

1. 作用

  • ApplicationContextInitializer(系统初始化器),在 Spring 容器化开始的时候,ApplicationContextInitializer接口的所有实现在类被实例化。
  • 在Spring容器刷新前,所有实现类的 org.springframework.context.ApplicationContextInitializer#initialize 方法会被调用,initialize 方法的形参类型是 ConfigurableApplicationContext,因此可以认为 ApplicationContextInitializer 实际上是Spring容器初始化前 ConfigurableApplicationContext 的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
 
   /**
    * Initialize the given application context.
    * @param applicationContext the application to configure
    */
   void initialize(C applicationContext);
 
}

2. 加载方式

通过具体的实现类实现ApplicationContextInitializer接口。

Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:

  1. 实现ApplicationContextInitializer接口
  2. 把实现类注册到Spring容器中

其中把实现了ApplicationContextInitializer接口的实现类注册到Spring容器中,主要有三种方式:

  • spring.factories
  • 启动类中配置
  • application.properties

以下一一介绍。

2.1. spring.factories

springboot会扫描所有jar包下的 META-INF/spring.factorties,比如预置以下内容,即可完成自定义MyApplicationContextInitializer的注册:

org.springframework.context.ApplicationContextInitializer=com.knqiufan.config.MyApplicationContextInitializer

2.2. 启动类中配置

在SpringBoot的启动类中,使用SpringApplication.addInitializers(new MyApplicationContextInitializer())完成自定义MyApplicationContextInitializer的注册:

@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(TestApplication.class);
        springApplication.addInitializers(new MyApplicationContextInitializer());
        springApplication.run(args);
    }
}

2.3. application.properties

在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册:

context.initializer.classes=com.knqiufan.config.MyApplicationContextInitializer

3. 初始化时机

ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中。

即Spring容器初始化开始前,先通过 org.springframework.boot.SpringApplication#getSpringFactoriesInstances 得到所有实现类的集合,然后通过 org.springframework.boot.SpringApplication#setInitializers 注入到SpringApplication类的initializers属性中:

/**
 * SpringApplication构造函数
 */
@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();
    // 获得所有实现类的集合
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    // 给属性 List<ApplicationContextInitializer<?>> initializers 赋值
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

org.springframework.boot.SpringApplication#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
    // 使用Set保证名称唯一,以防止重复
    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 静态方法来加载所有 ApplicationContextInitializer 的实现类名称。

SpringFactoriesLoader.loadFactoryNames 中调用了 loadSpringFactories 方法,在 loadSpringFactories 方法中就可以看到加载了 META-INF/spring.factories 文件,并将文件中的实现类加入到缓存中。源码如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        Map<String, List<String>> result = new HashMap();

        try {
            // 获取 META-INF/spring.factories 下配置的所有实现类
            Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
            
            // ... 省略部分代码 ...
            
            // 加载的ApplicationContextInitializer实现类加入缓存
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

4. 执行时机

ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext-->org.springframework.boot.SpringApplication#applyInitializers中,也就是Spring容器正式刷新前,准备上下文环境时。

/**
 * Spring容器准备上下文环境方法
 */
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 执行 ApplicationContextInitializer 实现类的 initialize() 方法
    applyInitializers(context);
    //... 省略其他代码 ...
}

/**
 * 在此方法中执行 ApplicationContextInitializer 实现类中的 initialize() 方法
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
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.");
        // 执行实现类的initialize方法
        initializer.initialize(context);
    }
}

浅浅看下 org.springframework.boot.SpringApplication#getInitializers 方法。

在前面初始化时,调用 org.springframework.boot.SpringApplication#setInitializers 方法将获取到的所有的 ApplicationContextInitializer 接口实现类加入到 initializers 属性中,在这里的 org.springframework.boot.SpringApplication#getInitializers 方法就负责将 initializers 属性中数据拿出来。

源码如下:

/**
 * 返回使用 Order 排序过的 ApplicationContextInitializer 实现类的Set列表
 * Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that
 * will be applied to the Spring {@link ApplicationContext}.
 * @return the initializers
 */
public Set<ApplicationContextInitializer<?>> getInitializers() {
    return asUnmodifiableOrderedSet(this.initializers);
}

private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
    List<E> list = new ArrayList<>(elements);
    list.sort(AnnotationAwareOrderComparator.INSTANCE);
    return new LinkedHashSet<>(list);
}

5. 内置实现类

Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现。简单介绍几个:

  • DelegatingApplicationContextInitializer
  • ContextIdApplicationContextInitializer
  • ConfigurationWarningsApplicationContextInitializer
  • ServerPortInfoApplicationContextInitializer

5.1. DelegatingApplicationContextInitializer

ApplicationContextInitializer 其中一个的实现方式就是在 application.properties 配置文件中对实现类进行配置。

DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法。

源码很好理解,如下:

/**
 * {@link ApplicationContextInitializer} that delegates to other initializers that are
 * specified under a {@literal context.initializer.classes} environment property.
 * 获取 context.initializer.classes 这个环境变量中配置的实现类,并执行
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @since 1.0.0
 */
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()) {
            // 执行实现类的 initialize 方法
			applyInitializerClasses(context, initializerClasses);
		}
	}

	private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
        // 获取 PROPERTY_NAME 环境变量名配置的所有实现类类名
		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);
		}
	}

    /**
     * 执行实现类的 initialize 方法
     */
	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));
		}
        // 执行实现类的 initialize 方法
		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());
        // 遍历所有 ApplicationContextInitializer 接口的实现类,执行实现类的 initialize 方法
		for (ApplicationContextInitializer initializer : initializers) {
			initializer.initialize(context);
		}
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

}

5.2. ContextIdApplicationContextInitializer

ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为 “application”。

以下只展示重点源码,有兴趣可以直接去查看完整代码:

public class ContextIdApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    // ... 省略部分代码 ...
    
	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		ContextId contextId = getContextId(applicationContext);
		applicationContext.setId(contextId.getId());
		applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
	}

	private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
		// ... 省略部分代码 ...
		return new ContextId(getApplicationId(applicationContext.getEnvironment()));
	}
    // 设置 Spring 应用上下文 ID,若没设置则默认为 “application”
	private String getApplicationId(ConfigurableEnvironment environment) {
		String name = environment.getProperty("spring.application.name");
		return StringUtils.hasText(name) ? name : "application";
	}

    // ... 省略部分代码 ...
}

5.3. ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置。

该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。

以下只展示重点源码,有兴趣可以直接去查看完整代码:

public class ConfigurationWarningsApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);

    /**
     * 增加了一个 Bean 的后置处理器,在注册 BeanDefinition 实例之后生效。
     * 用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。
     */
	@Override
	public void initialize(ConfigurableApplicationContext context) {
		context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
	}

	/**
	 * Returns the checks that should be applied.
	 * @return the checks to apply
	 */
	protected Check[] getChecks() {
		return new Check[] { new ComponentScanPackageCheck() };
	}

	/**
	 * {@link BeanDefinitionRegistryPostProcessor} to report warnings.
	 */
	protected static final class ConfigurationWarningsPostProcessor
			implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {

        // ... 省略部分代码 ...

		@Override
		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
			for (Check check : this.checks) {
				String message = check.getWarning(registry);
				if (StringUtils.hasLength(message)) {
					warn(message);
				}
			}

		}
        // 输出警告信息日志
		private void warn(String message) {
			if (logger.isWarnEnabled()) {
				logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
			}
		}

	}

    // ... 省略部分代码 ...
}

5.4. ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中。

public class ServerPortInfoApplicationContextInitializer implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, ApplicationListener<WebServerInitializedEvent> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
        // 注册为监听器
		applicationContext.addApplicationListener(this);
	}

	@Override
	public void onApplicationEvent(WebServerInitializedEvent event) {
		String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
		setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
	}
    // ... 省略部分代码 ...

}

6. 总结

  • 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法都会被调用,所以可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作。
  • ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的
  • 深入理解了ApplicationContextInitializer接口实现类的初始化时机和执行时机
  • 了解了一些内部实现类的作用和实现方法,可以学习到Spring本身是如何利用扩展接口实现一些功能,在实际的项目开发中具有一定的参考意义。