Spring Boot -- 钩子接口使用及原理
目录
一、钩子接口介绍
Spring 提供了非常多的扩展接口,官方将这些接口称之为钩子,这些钩子会在特定的时间被回调,以此来增强 Spring 功能,众多优秀的框架也是通过扩展这些接口,来实现自身特定的功能,如 SpringBoot、mybatis 等。
二、Aware接口
Aware从字面的意思理解就是"知道"、“感知”的意思,是用来获取Spring内部对象的接口。Aware自身是一个顶级接口,它有一系列子接口,在一个 Bean 中实现这些子接口并重写里面的 set 方法后,Spring 容器启动时,就会回调该 set 方法,而相应的对象会通过方法参数传递进去。我们以其中的 ApplicationContextAware 接口为例。
2.1、ApplicationContextAware
大部分 Aware 系列接口都有一个规律,它们以对象名称为前缀,获取的就是该对象,所以 ApplicationContextAware 获取的对象是 ApplicationContext 。
public interface ApplicationContextAware extends Aware {
/**
* Set the ApplicationContext that this object runs in.
* Normally this call will be used to initialize the object.
* <p>Invoked after population of normal bean properties but before an init callback such
* as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
* or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
* {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
* {@link MessageSourceAware}, if applicable.
* @param applicationContext the ApplicationContext object to be used by this object
* @throws ApplicationContextException in case of context initialization errors
* @throws BeansException if thrown by application context methods
* @see org.springframework.beans.factory.BeanInitializationException
*/
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
ApplicationContextAware 源码非常简单,其继承了 Aware 接口,并定义一个 set 方法,参数就是 ApplicationContext 对象,当然,其它系列的 Aware 接口也是类似的定义。其具体使用方式如下:
package com.zy.example;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Data
@Slf4j
@Component
public class AwareTest implements ApplicationContextAware {
/*
* 保存应用上下文
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
//输出所有BeanDefinition name
for(String name:applicationContext.getBeanDefinitionNames()) {
log.info(name);
}
}
}
在 Spring 启动过程中,会回调 setApplicationContext 方法,并传入 ApplicationContext 对象,之后就可对该对象进行操作。我们获取到ApplicationContext对象,并将所有BeanDefinition名称输出:
其它系列的 Aware 接口也是如此使用。具体的调用时机会在后面详细介绍。
以下是几种常用的 Aware 接口:
- BeanFactoryAware:获取 BeanFactory 对象,它是基础的容器接口。
- BeanNameAware:获取 Bean 的名称。
- EnvironmentAware:获取 Environment 对象,它表示整个的运行时环境,可以设置和获取配置属性。
- ApplicationEventPublisherAware:获取 ApplicationEventPublisher 对象,它是用来发布事件的。
- ResourceLoaderAware:获取 ResourceLoader 对象,它是获取资源的工具。
三、InitializingBean, DisposableBean
3.1、InitializingBean
InitializingBean 是一个可以在 Bean 的生命周期执行自定义操作的接口,凡是实现该接口的 Bean,在初始化阶段都可以执行自定义的操作。
public interface InitializingBean {
/**
* Invoked by the containing {@code BeanFactory} after it has set all bean properties
* and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
* <p>This method allows the bean instance to perform validation of its overall
* configuration and final initialization when all bean properties have been set.
* @throws Exception in the event of misconfiguration (such as failure to set an
* essential property) or if initialization fails for any other reason
*/
void afterPropertiesSet() throws Exception;
}
从 InitializingBean 源码中可以看出它有一个 afterPropertiesSet 方法,当一个 Bean 实现该接口时,在 Bean 的初始化阶段,会回调 afterPropertiesSet 方法,其初始化阶段具体指 Bean 设置完属性之后。
3.2、DisposableBean
同理,DisposableBean在Bean销毁时执行自定义的操作,必须资源的释放。
public interface DisposableBean {
/**
* Invoked by the containing {@code BeanFactory} on destruction of a bean.
* @throws Exception in case of shutdown errors. Exceptions will get logged
* but not rethrown to allow other beans to release their resources as well.
*/
void destroy() throws Exception;
}
比如:
package com.zy.example;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Component;
@Data
@Component
@Slf4j
public class BeanTest implements InitializingBean, DisposableBean {
/*
* 构造函数
*/
public BeanTest(){
log.info("New object...");
}
@Override
public void destroy() {
log.info("Destroying ...");
}
@Override
public void afterPropertiesSet() {
log.info("Initializing ....");
}
}
四、BeanPostProcessor、BeanFactoryPostProcessor
4.1、BeanPostProcessor
BeanPostProcessor 和 InitializingBean 有点类似,也是可以在 Bean 的生命周期执行自定义操作,一般称之为 Bean 的后置处理器,不同的是, BeanPostProcessor 可以在 Bean 初始化前、后执行自定义操作,且针对的目标也不同,InitializingBean 针对的是实现 InitializingBean 接口的 Bean,而 BeanPostProcessor 针对的是所有的 Bean。并且postProcessBeforeInitialization在对象创建之后,afterPropertiesSet之前执行,而postProcessAfterInitialization在afterPropertiesSet之后执行:
public interface BeanPostProcessor {
// Bean 初始化前调用
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// Bean 初始化后调用
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
所有的 Bean 在初始化前、后都会回调接口中的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,入参是当前正在初始化的 Bean 对象和 BeanName。值得注意的是 Spring 内置了非常多的 BeanPostProcessor ,以此来完善自身功能,这部分会在后面文章深入讨论。
我们扩充我们的测试类AwareTest :
package com.zy.example;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Data
@Slf4j
@Component
public class AwareTest implements ApplicationContextAware, BeanPostProcessor {
/*
* 保存应用上下文
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
//输出所有BeanDefinition name
for(String name:applicationContext.getBeanDefinitionNames()) {
log.info(name);
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if(beanName.equals("beanTest")) {
log.info("postProcessBeforeInitialization:" + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if(beanName.equals("beanTest")) {
log.info("postProcessAfterInitialization:" + beanName);
}
return bean;
}
}
BeanTest :
package com.zy.example;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Component;
@Data
@Component
@Slf4j
public class BeanTest implements InitializingBean, DisposableBean, BeanNameAware {
/*
* 保存当前bean name
*/
private String beanName;
/*
* 构造函数
*/
public BeanTest(){
log.info("New object...");
}
@Override
public void destroy() {
log.info("Destroying ...");
}
@Override
public void afterPropertiesSet() {
log.info("Initializing ....");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
log.info("Current bean name:" + name);
}
}
可以看到beanTest对象先是被实例化出来,然后执行BeanPostProcessor的postProcessBeforeInitialization,再执行InitializingBean的afterPropertiesSet,最后执行BeanPostProcessor的postProcessAfterInitialization方法。而ApplicationContextAware的setApplicationContext方法执行时所有BeanDefinition都已加载,但还未实例化Bean
BeanPostProcessor 使用场景其实非常多,因为它可以获取正在初始化的 Bean 对象,然后可以对Bean 对象做一些定制化的操作,如:判断该 Bean 是否为某个特定对象、获取 Bean 的注解元数据等。事实上,Spring 内部也正是这样使用的,之前我们介绍的Spring Boot -- Spring AOP原理及简单实现手写AOP时也是利用了BeanPostProcessor的特性,我们对@Pointcut注解指定的Bean都进行了代理处理。
4.2、BeanFactoryPostProcessor
BeanFactoryPostProcessor 是 Bean 工厂的后置处理器,一般用来修改上下文中的 BeanDefinition,修改 Bean 的属性值。
public interface BeanFactoryPostProcessor {
// 入参是一个 Bean 工厂:ConfigurableListableBeanFactory。该方法执行时,所有 BeanDefinition 都已被加载,但还未实例化 Bean。
// 可以对其进行覆盖或添加属性,甚至可以用于初始化 Bean。
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
BeanFactoryPostProcessor 源码非常简单,其提供了一个 postProcessBeanFactory 方法,当所有的 BeanDefinition 被加载时,该方法会被回调。值得注意的是,Spring 内置了许多 BeanFactoryPostProcessor 的实现,以此来完善自身功能。 这里,我们来实现一个自定义的 BeanFactoryPostProcessor:
package com.zy.example;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Data
@Slf4j
@Component
public class AwareTest implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
log.info("------------------------------------------postProcessBeanFactory-------------------------");
String beanNames[] = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
log.info(beanDefinition.toString());
}
}
}
主要是通过 Bean 工厂获取所有的 BeanDefinition 。
可以看到,BeanDefinition 正确输出,里面是一些 Bean 的相关定义,如:是否懒加载、Bean 的 Class 以及 Bean 的属性等。
五、Import
在博客Spring Boot -- Spring AOP原理及简单实现中我们已经粗略的介绍过整个注解了,Import经常用在@Enable 模块注解中,比如:@EnableDiscoveryClient,用于将应用注册到Eureka Server并将Eureka Server有的服务拉取到微服务系统。点开EnableDiscoveryClient源码,便会发现里面用到了@import注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
此外,还有之前我们介绍到的@EnableConfigurationProperties注解,用来注册 Properties 配置类和绑定配置属性:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
/**
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
那么,这个@import的作用是什么呢,它是如何工作的呢?我们在项目里如何应用@import导入我们自定义的类?
5.1、导了一个普通的类
Spring 3.0之前,我们的Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内。Spring 3.0之后提供了JavaConfig的方式,也就是将Spring IOC容器里Bean的元信息以java代码的方式进行描述。我们可以通过@Component与@Bean这两个注解配合使用来将原来配置在xml文件里的bean通过java代码的方式进行描述。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
//这里说了可以配合 Configuration , ImportSelector, ImportBeanDefinitionRegistrar 来使用噢 或者常用的(regular component classes )也就是Bean
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
从源码里可以看出Import可以配合 Configuration , ImportSelector, ImportBeanDefinitionRegistrar 来使用,下面的or表示也可以把Import当成普通的Bean来使用,只是使用方式上有点区别,@Import只允许放到类上面,不能放到方法上。下面我们来看具体的使用方式。
我们移除BeanTest类上的@Component注解,这里我们可以利用Import将BeanTest导入Spring容器中,直接将类的class加到Import的value里即可:
@Configuration
@Import(value={BeanTest.class})
public class Config {
}
这种方式注入类在Spring内部用的并不多。
5.2、通过ImportBeanDefinitionRegistrar注册Bean
Import注解通过配合ImportBeanDefinitionRegistrar类将类注入Spring IOC容器里。ImportBeanDefinitionRegistrar类的源码如下:
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
从上面的代码可以看出在注入Spring IOC容器的时候,我们肯定是通过registry这个变量了,而importingClassMetadata可以得到被@Import注解修饰的类的所有元数据对象。我们自定义的BeanTestRegistrar类定义如下:
package com.zy.example;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class BeanTestRegistrar implements ImportBeanDefinitionRegistrar {
/*
* 一般通过 AnnotationMetadata 进行业务判断,然后通过 BeanDefinitionRegistry 直接注册 Bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanTest.class);
registry.registerBeanDefinition(BeanTest.class.getName(), beanDefinition);
}
}
此时我们已经移除了BeanTest类上的@Cofiguration注解,所以,BeanTest不会自动加入到 Spring 容器中。并修改App类。代码如下:
@SpringBootApplication
@Import(value = BeanTestRegistrar.class)
public class App {
public static void main(String[] args){
//整个程序入口 启动Spring Boot项目
SpringApplication.run(App.class,args);
}
}
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步