此前的文章中,我们详细介绍了 Spring 预留的六个可以用来扩展代码的扩展点,从而让我们能够在 Spring 的启动阶段添加我们需要的业务启动代码:
事实上,Spring 的扩展点还有很多,只是通常如果我们仅仅是添加业务启动代码,或是对运行参数进行初始化解析,使用上述六个扩展点就已经可以满足需求。
但是,如果我们想要从根本上改变一些 Spring 的默认行为,例如修改 Context 中的属性,或者要改变 bean 的创建加载行为,这时,我们就必须在 SpringBoot 生命周期的最早期进行扩展。
SpringBoot 为我们预留了这样的扩展点,它就是本文将要介绍的 ApplicationContextInitializer。
SpringBoot 在 Spring 的基础上,遵循约定大于配置原则,加入了许许多多自动装配的机制。让我们在给别人提供组件时,只需要按照 SpringBoot 的规范实现我们自己的扩展,别人无需任何配置就可以自动装配我们的组件。ApplicationContextInitializer 就是可以被这样自动装配的扩展点。
SpringBoot 会在完成 ConfigurableApplicationContext 的构建后,在容器刷新之前,自动调用已经被装配进 SpringBoot 容器的 ApplicationContextInitializer 接口实现类,执行它的 initialize 方法。
ApplicationContextInitializer 接口的定义如下:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
如果这个接口有多个实现类,SpringBoot 支持通过 @Order 注解或实现 Ordered 接口指定每个实现类的执行优先级。
如果我们实现了上述 ApplicationContextInitializer 接口,如何将我们的实现类添加到 SpringBoot 的启动中呢?有三种方法。
通过 SpringApplication 对象的 addInitializers 方法,可以添加多个 ApplicationContextInitializer 实现类:
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MySpringBootApplication.class);
application.addInitializers(new MyApplicationContextInitializer());
application.run(args);
}
}
SpringBoot 启动时,会扫描 classpath,寻找 application.properties 配置,来找到启动过程中需要调用的相关组件。
在这个配置文件中,加入 ApplicationContextInitializer 实现类的配置,就可以实现让 SpringBoot 自动加载该实现类。
context.initializer.classes=org.springframework.boot.demo.common.MyApplicationContextInitializer
在 classpath 下的 META-INF/spring.factories 中配置相应的实现类也同样可以配置 ApplicationContextInitializer 实现类的自动加载和启动:
org.springframework.context.ApplicationContextInitializer=org.springframework.boot.demo.common.MyApplicationContextInitializer
在 SpringBoot 中,创建 bean 有多种方法:
这些方式我们已经很熟悉了,但有时,我们希望完全自己构造出一个 bean 对象,并且由于它受到其它 bean 的依赖,需要在启动最初的阶段被加载到 SpringBoot 的上下文环境中,这时,通过 ApplicationContextInitializer 来实现就是一个很好的选择。
下面的例子展示了通过 ApplicationContextInitializer 的实现类创建 bean:
public class BootInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext genericApplicationContext) {
String beanName = "BootInitializeBean";
Object beanObject = generateBeanObject(beanName);
genericApplicationContext.registerBean(beanName, beanObject.getClass(), (Supplier) () -> beanObject, bd -> {
bd.setAutowireCandidate(false);
bd.setLazyInit(true);
});
}
}
在这个例子中,调用了回调参数 GenericApplicationContext 对象的 registerBean 方法,指定了 bean 的 name、类型、对象以及 bean 属性。
值得提的是第四个参数,该参数是可选的,它是用来指定 bean 属性的 BeanDefinitionCustomizer 接口实现。
当然了,这只是一个例子,除此以外,ApplicationContextInitializer 还有多种用途,例如将 beanFactory 替换成你自己实现的对象,来实现你想要的独特的 bean 加载方式等等。