3.5 ApplicationContextInitializer加载
3.5.1 源码解析
ApplicationContextInitializer是Spring IOC容器提供的一个接口,它是一个回调接口,主要目的是允许用户在ConfigurableApplicationContext类型(或其子类型)的ApplicationContext做refresh方法调用刷新之前,对ConfigurableApplicationContext实例做进一步的设置或处理。通常用于应用程序上下文进行编程初始化的Web应用程序中。
ApplicationContextInitializer接口只定义了一个initialize方法,代码如下。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { void initialize(C applicationContext); }
ApplicationContextInitializer接口的initialize方法主要是为了初始化指定的应用上下文。而对应的上下文由参数传入,参数为ConfigurableApplicationContext的子类。
在完成了Web应用类型推断之后,ApplicationContextInitializer便开始进行加载工作,该过程可分两步骤:获得相关实例和设置实例。对应的方法分别为getSpringFactoriesInstances和setInitializers。
SpringApplication中获得实例相关方法代码如下。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // 加载对应配置,这里采用LinkedHashSet和名称来确保加载的唯一性 Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 创建实例 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 排序操作 AnnotationAwareOrderComparator.sort(instances); return instances; }
getSpringFactoriesInstances方法依然是通过SpringFactoriesLoader类的loadFactoryNames方法来获得META-INF/spring.factories文件中注册的对应配置。在Spring Boot 2.2.1版本中,该文件内具体的配置代码如下。
# 应用程序上下文的初始化器配置 org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
配置代码中等号后面的类为接口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 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; }
完成获取配置类集合和实例化操作之后,调用setInitializers方法将实例化的集合添加到SpringApplication的成员变量initializers中,类型为List<ApplicationContextInitiali-zer<?>>,代码如下。
private List<ApplicationContextInitializer<?>> initializers; public void setInitializers( Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<>( initializers); }
setInitializers方法将接收到的initializers作为参数创建了一个新的List,并将其赋值给SpringApplication的initializers成员变量。由于是创建了新的List,并且直接赋值,因此该方法一旦被调用,便会导致数据覆盖,使用时需注意。
3.5.2 实例讲解
阅读完源代码,我们进行一些拓展,来自定义一个ApplicationContextInitializer接口的实现,并通过配置使其生效。
这里以实现ApplicationContextInitializer接口,并在initialize方法中打印容器中初始化了多少个Bean对象为例来进行演示,代码如下。
@Order(123) // @Order的value值越小越早执行 public class LearnApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 打印容器里面初始化了多少个Bean System.out.println("容器中初始化Bean数量:" + applicationContext.getBean- DefinitionCount()); } }
上面就完成了一个最基础的ApplicationContextInitializer接口的实现类。当我们定义好具体的类和功能之后,可通过3种方法调用该类。
第一种方法就是参考Spring Boot源代码中的操作,将该实现类配置于META-INF/spring.factories文件中,这种方法与上面讲到的源代码配置方法一致。
第二种方法是通过application.properties或application.yml文件进行配置,格式如下。
context.initializer.classes=com.secbro2.learn.initializer.LearnApplicationCont- extInitializer
这种方法是通过DelegatingApplicationContextInitializer类中的initialize方法获取到配置文件中对应的context.initializer.classes的值,并执行对应的initialize方法。
第三种方法是通过SpringApplication提供的addInitializers方法进行追加配置,代码如下。
public static void main(String[] args) { SpringApplication app = new SpringApplication(SpringLearnApplication.class, Person.class); // 添加自定义ContextInitializer,注意会覆盖掉默认配置的 app.addInitializers(new LearnApplicationContextInitializer()); app.run(args); }
无论通过以上3种方法的哪一种,配置完成后,执行启动程序都可以看到控制台打印容器中Bean数量的日志。