强大!牢记这16个Spring Boot扩展接口,代码优雅提升一个层次
Spring的核心理念是它的容器。当容器刷新时,表面看似风平浪静,内部却如同风起云涌的大海,广阔而汹涌。SpringBoot更进一步,将Spring封装起来,遵循“约定优于配置”的原则,并结合自动配置机制。通常情况下,只需添加一个依赖,我们就能以最小配置甚至零配置实现功能。
我尤其喜欢自动配置机制,因此在开发中间件和通用依赖工具时经常使用这个功能。这种方法允许用户以最低的成本进行集成。要掌握自动配置,必须了解Spring Bean的构建生命周期以及各种扩展接口。当然,理解Bean的不同生命周期也能帮助更深入地理解Spring,业务代码也可以合理地利用这些扩展点编写更加优雅的代码。
在本文中,我总结了几乎所有Spring
和SpringBoot
的扩展接口及其应用场景。同时,我整理了Bean从加载到最终初始化过程中所有可扩展点的时序图,这使我们能够一窥Bean是如何逐步加载到Spring容器中的。
文章内容较长,请耐心阅读!
启动期间可扩展接口调用的时序图
以下是Bean在Spring容器中的生命周期中所有可扩展点的顺序图。
接下来我将逐一分析每一个。
ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer
这是一个用于在整个Spring容器刷新之前初始化ConfigurableApplicationContext
的回调接口。简单来说,在容器刷新之前,会调用该类的initialize
方法,此时允许用户扩展。用户可以在整个Spring容器初始化之前做一些事情。
可能的使用场景包括在最初激活某些配置,或利用类加载器加载类之前的时机执行如动态字节码注入等操作。
扩展方法如下:
public class TestApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("[ApplicationContextInitializer]");
}
}
由于此时Spring容器尚未初始化,因此有三种方式使你的扩展生效:
在启动类中添加
springApplication.addInitializers(new TestApplicationContextInitializer())
。在配置文件中设置
context.initializer.classes=com.example.demo.TestApplicationContextInitializer
。使用Spring的SPI扩展,在
spring.factories
中添加org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer
。
BeanDefinitionRegistryPostProcessor
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
该接口是在读取项目中的beanDefinition
后执行的,提供了一个补充的扩展点。
使用场景:你可以在此处动态注册自定义的beanDefinition
,并加载类路径之外的Bean。
扩展方法如下:
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanFactory");
}
}
BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
该接口是对beanFactory
的扩展,它的调用发生在Spring读取完beanDefinition
信息之后,Bean实例化之前。
在这个阶段,用户可以通过实现该扩展接口来处理某些任务,如修改已注册的beanDefinition
的元数据。
扩展方法如下:
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanFactoryPostProcessor]");
}
}
InstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
此接口继承自BeanPostProcessor
接口,区别在于:
BeanPostProcessor
接口只在Bean的初始化阶段(即注入Spring上下文之前和之后)扩展,而InstantiationAwareBeanPostProcessor
接口增加了三个方法,扩展了Bean的实例化和属性注入阶段的作用范围。
该类的主要扩展点是以下五个方法,它们在Bean生命周期的实例化阶段和初始化阶段发挥作用。按调用顺序如下:
postProcessBeforeInstantiation
: 在实例化Bean之前,相当于在创建(new)Bean之前。postProcessAfterInstantiation
: 在实例化Bean之后,相当于创建(new)Bean之后。postProcessPropertyValues
: Bean实例化后,在属性注入阶段触发。像@Autowired
、@Resource
等注解的原理就基于这个方法。postProcessBeforeInitialization
: Bean初始化之前,相当于在Bean注入Spring上下文之前。postProcessAfterInitialization
: Bean初始化之后,相当于在Bean注入Spring上下文之后。
使用场景:该扩展点在中间件开发和业务逻辑中都非常有用。例如,可以在Bean生命周期的不同阶段收集实现某个接口的Bean,或为某种类型的Bean统一设置属性等。
扩展方法如下:
public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before initialization " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after initialization " + beanName);
return bean;
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before instantiation " + beanName);
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after instantiation " + beanName);
return true;
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues " + beanName);
return pvs;
}
SmartInstantiationAwareBeanPostProcessor
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor
该扩展接口有三个触发方法:
predictBeanType
: 在postProcessBeforeInstantiation
之前触发(在时序图中未标出,通常不需要扩展此点)。该方法用于预测Bean的类型,返回第一个成功预测的Class类型,如果无法预测则返回null。当调用BeanFactory.getType(name)
且无法通过Bean名称确定类型信息时,该回调方法用于决定类型信息。determineCandidateConstructors
: 在postProcessBeforeInstantiation
之后触发,用于确定Bean的构造函数,返回Bean的所有构造函数列表。用户可以扩展此点以自定义选择适当的构造函数来实例化Bean。getEarlyBeanReference
: 在postProcessAfterInstantiation
之后触发。在存在循环依赖的场景下,Bean实例化后,为了防止循环依赖,提前暴露回调方法,用于实例化后的Bean进行后处理。
扩展方法如下:
public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] predictBeanType " + beanName);
return beanClass;
}
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors " + beanName);
return null;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference " + beanName);
return bean;
}
}
BeanFactoryAware
org.springframework.beans.factory.BeanFactoryAware
此类只有一个触发点,即在Bean实例化后、属性注入之前(即Setter方法之前)触发。该类的扩展点方法是setBeanFactory
,当用户想要获取当前BeanFactory
的引用时,可以扩展此接口来获取。
扩展方法如下:
public class TestBeanFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("[TestBeanFactoryAware] " + beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName());
}
}
ApplicationContextAwareProcessor
org.springframework.context.support.ApplicationContextAwareProcessor
虽然这个类本身没有扩展点,但它内部提供了六个扩展点用于实现。这些扩展点是在bean实例化后、初始化之前触发的。
正如你所看到的,这个类用于在bean实例化并填充属性之后执行各种驱动接口。通过执行上述突出显示的扩展接口,可以获得相应的容器变量。因此,这里实际上有六个扩展点,我将一起讨论:
EnvironmentAware
: 用于获取EnvironmentAware
的扩展类。这个变量非常有用,可以访问系统中的所有参数。个人认为,没必要扩展这个Aware
,因为Spring内部已经支持通过注入直接获取。EmbeddedValueResolverAware
: 用于获取StringValueResolver
的扩展类。StringValueResolver
用于获取基于字符串的属性变量。通常我们使用@Value
注解获取这些变量,但如果实现了这个Aware
接口并缓存StringValueResolver
,就可以使用它来获取基于字符串的变量,效果相同。ResourceLoaderAware
: 用于获取ResourceLoader
的扩展类。ResourceLoader
可以访问类路径中的所有资源对象。你可以扩展这个类来获取ResourceLoader
对象。ApplicationEventPublisherAware
: 用于获取ApplicationEventPublisher
的扩展类。ApplicationEventPublisher
用于发布事件,通常与ApplicationListener
结合使用,我将在后面详细介绍。此对象也可以通过Spring注入获得。MessageSourceAware
: 用于获取MessageSource
的扩展类。MessageSource
主要用于国际化。ApplicationContextAware
: 用于获取ApplicationContext
的扩展类。许多人都熟悉ApplicationContext
,它是Spring的上下文管理器,允许手动访问Spring上下文中注册的任何bean。我们经常扩展这个接口来缓存Spring上下文,并将其包装成静态方法。此外,ApplicationContext
还实现了BeanFactory
、MessageSource
、ApplicationEventPublisher
等接口,可以用于相关任务。
BeanNameAware
org.springframework.beans.factory.BeanNameAware
可以看出,这个类也是一种 Aware
扩展。其触发点发生在bean初始化之前,即 postProcessBeforeInitialization
之前。这个类只有一个触发点方法:setBeanName
。
使用场景:用户可以扩展此点,在初始化bean之前获取Spring容器中注册的beanName,然后根据需要修改这个beanName的值。
扩展方法:
public class NormalBeanA implements BeanNameAware{
public NormalBeanA() {
System.out.println("NormalBean constructor");
}
@Override
public void setBeanName(String name) {
System.out.println("[BeanNameAware] " + name);
}
}
@PostConstruct
javax.annotation.PostConstruct
这不是一个扩展点,而是一种标记。它的作用是在bean初始化阶段。如果某个方法被 @PostConstruct
注解标记,那么该方法将首先被调用。需要注意的是,这个标准的具体触发点是在 postProcessBeforeInitialization
之后、InitializingBean.afterPropertiesSet
之前。
使用场景:用户可以通过注解特定方法来初始化某个特定属性。
扩展方法:
public class NormalBeanA {
public NormalBeanA() {
System.out.println("NormalBean constructor");
}
@PostConstruct
public void init(){
System.out.println("[PostConstruct] NormalBeanA");
}
}
InitializingBean
org.springframework.beans.factory.InitializingBean
顾名思义,这个类也用于bean的初始化。InitializingBean
接口为bean提供了一个初始化方法,它只有一个方法 afterPropertiesSet
。任何继承此接口的类将在bean初始化过程中执行此方法。此扩展的触发点在 postProcessAfterInitialization
之前。
使用场景:用户可以实现此接口,在系统启动时初始化某些业务指标。
扩展方法:
public class NormalBeanA implements InitializingBean{
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("[InitializingBean] NormalBeanA");
}
}
FactoryBean
org.springframework.beans.factory.FactoryBean
在正常情况下,Spring使用反射机制和bean的类属性来实例化bean。但在某些情况下,bean的实例化过程可能非常复杂,如果按照传统方式进行,则需要在bean中配置大量信息,配置方法的灵活性有限。在这种情况下,编码的方式可能会更简单。为此,Spring提供了 org.springframework.beans.factory.FactoryBean
接口,允许用户自定义bean实例化的逻辑。
FactoryBean
接口在Spring框架中具有重要地位。Spring自身提供了超过70种 FactoryBean
实现,它们隐藏了某些复杂bean实例化的细节,给高级应用带来了方便。从Spring 3.0开始,FactoryBean
支持泛型,接口声明变为 FactoryBean<T>
。
使用场景:用户可以扩展此类,为他们希望实例化的bean创建代理。例如,他们可以拦截对象的所有方法,在每次调用之前和之后输出一行日志,模拟 ProxyFactoryBean
的功能。
扩展方法:
public class TestFactoryBean implements FactoryBean<TestFactoryBean.TestFactoryInnerBean> {
@Override
public TestFactoryBean.TestFactoryInnerBean getObject() throws Exception {
System.out.println("[FactoryBean] getObject");
return new TestFactoryBean.TestFactoryInnerBean();
}
@Override
public Class<?> getObjectType() {
return TestFactoryBean.TestFactoryInnerBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
public static class TestFactoryInnerBean{
}
}
SmartInitializingSingleton
org.springframework.beans.factory.SmartInitializingSingleton
这个接口只有一个方法 afterSingletonsInstantiated
,其目的是作为回调接口,在Spring容器管理的所有单例对象(非延迟加载对象)初始化后调用。它的触发点是在 postProcessAfterInitialization
之后。
使用场景:用户可以扩展此接口,在所有单例对象完全初始化后执行一些后处理业务。
扩展方法:
public class TestSmartInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
System.out.println("[TestSmartInitializingSingleton]");
}
}
CommandLineRunner
org.springframework.boot.CommandLineRunner
这个接口也只有一个方法:run(String... args)
。它的触发点是在整个项目启动之后,自动执行。如果有多个 CommandLineRunner
实例,可以使用 @Order
注解进行排序。
使用场景:用户可以扩展此接口,在项目启动后进行一些业务预处理。
扩展方法:
public class TestCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("[TestCommandLineRunner]");
}
}
** DisposableBean**
org.springframework.beans.factory.DisposableBean
这个扩展点也只有一个方法:destroy()
。它的触发点是在对象被销毁时,自动执行此方法。例如,当运行 applicationContext.registerShutdownHook
时,此方法将被触发。
扩展方法:
public class NormalBeanA implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("[DisposableBean] NormalBeanA");
}
}
ApplicationListener
org.springframework.context.ApplicationListener
严格来说,这不应被视为Spring & Spring Boot中的扩展点。ApplicationListener
可以监听特定的事件 (event
)。触发时机可以穿插在业务方法执行过程中,允许用户定义自己的业务事件。
然而,Spring内部有一些内置事件。这些事件可以与启动过程交织在一起。我们也可以利用此功能,为这些内置事件创建自己的监听器,达到与之前某些触发点类似的效果。
让我们列出Spring中的一些主要内置事件:
ContextRefreshedEvent: 当
ApplicationContext
初始化或刷新时发布此事件。这也可以在ConfigurableApplicationContext
接口中使用refresh()
方法时发生。这里的初始化是指所有Beans成功加载、后置处理器Beans被检测并激活、所有单例Beans被预实例化,并且ApplicationContext
容器已准备好使用。ContextStartedEvent : 当使用
ConfigurableApplicationContext
(ApplicationContext的子接口)中的start()
方法启动ApplicationContext
时发布此事件。在spring
中,您可以使用start()
和stop()
方法控制ApplicationContext
的生命周期。启动容器后,可以通过stop()
停止容器。当容器启动时,您可以通过getLifecycle()
方法获取所有Lifecycle
接口的Bean,并激活它们的start()
方法。这通常用于具有后台任务的Bean。ContextStoppedEvent : 与
ContextStartedEvent
相反,stop()
方法会触发ContextStoppedEvent
事件。ContextClosedEvent: 当使用
ConfigurableApplicationContext
中的close()
方法关闭ApplicationContext
时,发布此事件。关闭的上下文context
不会被重新启动或刷新。RequestHandledEvent: Web应用程序中特有的事件。它表示Web请求的完成(只有在使用Spring的
DispatcherServlet
时才适用)。ApplicationFailedEvent: 该事件在启动Spring Boot时遇到异常时触发。
总结
通过这些 Spring 和 Spring Boot 的扩展点,我们可以大致了解一个 bean 的整个生命周期。在业务开发或中间件业务编写过程中,我们可以合理利用 Spring 提供的扩展点,在 Spring 启动的各个阶段执行特定操作,从而实现自定义初始化的目的。