当前位置: 首页 > news >正文

Spring 源码学习 2:Bean 后处理器

Spring 源码学习 2:Bean 后处理器

在前篇中介绍了,Spring 提供了一些 bean 的后处理器(实现了 BeanPostProsessor 接口),用于处理 bean 的依赖注入等。实际上,我们可以通过自定义一个实现了相应接口的后处理器来观察 bean 的生命周期。

Bean 生命周期

创建一个用于打印不同生命周期方法钩子的 bean 后处理器:

static class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeDestruction() is called.");}@Overridepublic boolean requiresDestruction(Object bean) {return DestructionAwareBeanPostProcessor.super.requiresDestruction(bean);}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeInstantiation() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessAfterInstantiation() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessProperties() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeInitialization() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessAfterInitialization() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}

创建一个 Bean,在不同的阶段打印信息:

static class MyBean {private ApplicationContext applicationContext;public MyBean() {System.out.println("MyBean's constructor is called.");}@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext) {System.out.println("MyBean's dependency injection method is called.");this.applicationContext = applicationContext;}@PostConstructpublic void postConstruct() {System.out.println("MyBean's postConstruct is called.");}@PreDestroypublic void preDestroy() {System.out.println("MyBean's preDestroy is called.");}
}

创建配置类,用 bean 方法添加这两个 bean:

@Configuration
static class Config {@BeanMyBeanPostProcessor myBeanPostProcessor() {return new MyBeanPostProcessor();}@BeanMyBean myBean() {return new MyBean();}
}

这里用测试类通过加载 Spring 上下文的方式进行测试,相关内容可以阅读这里。

输出:

postProcessBeforeInstantiation() is called.
MyBean's constructor is called.
postProcessAfterInstantiation() is called.
postProcessProperties() is called.
MyBean's dependency injection method is called.
postProcessBeforeInitialization() is called.
MyBean's postConstruct is called.
postProcessAfterInitialization() is called.
postProcessBeforeDestruction() is called.
MyBean's preDestroy is called.

可以看到执行顺序如下:

  1. postProcessBeforeInstantiation()钩子被调用。
  2. bean 工厂通过相应 bean 的构造器创建 bean 实例。
  3. postProcessAfterInstantiation()钩子被调用。
  4. postProcessProperties()钩子被调用。
  5. bean 工厂对实例进行依赖注入。
  6. postProcessBeforeInitialization()钩子被调用。
  7. bean 的postConstruct()方法被调用(通常在这里执行用户自定义初始化)。
  8. postProcessAfterInitialization钩子被调用。
  9. postProcessBeforeDestruction钩子被调用。
  10. bean 的preDestroy()方法被调用(通常在这里执行资源释放和环境清理)。

可以看到 bean 的生命周期主要包含这么几部分:

  • 实例化(通过构造器)
  • 依赖注入(属性注入或方法注入)
  • 初始化
  • 销毁

在相应的阶段前后,bean 后处理器中对应的生命周期钩子都会被调用。

模版方法模式

实际上这里应用了设计模式中的模版方法模式。

下面展示如何用模版方法模式实现一个在创建 Bean 时使用生命周期钩子的最简单的 Bean 工厂。

首先定义生命周期钩子接口:

interface LifeCycleHook {void beforeConstruct();<T> void afterConstruct(T instance);
}

定义自定义的 bean 工厂:

static class MyBeanFactory {private final List<LifeCycleHook> lifeCycleHooks = new ArrayList<>();public void add(LifeCycleHook lifeCycleHook) {lifeCycleHooks.add(lifeCycleHook);}public <T> T getBean(Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Constructor<T> constructor = clazz.getConstructor();constructor.setAccessible(true);// 调用实例化前钩子for (LifeCycleHook lifeCycleHook : lifeCycleHooks) {lifeCycleHook.beforeConstruct();}// 实例化T newInstance = constructor.newInstance();// 调用实例化后钩子for (LifeCycleHook lifeCycleHook : lifeCycleHooks) {lifeCycleHook.afterConstruct(newInstance);}return newInstance;}
}

在 bean 工厂中,通过反射的方式获取构造器,并创建实例。为了简单起见,这里默认类型拥有一个无参构造器。

bean 工厂持有一个生命周期钩子列表,在实例化前后会依次执行生命周期钩子中的对应方法。

注解处理和工作原理

除了实现特定接口以介入 Bean 的生命周期外,Bean 的后处理器还能够处理 Bean 中的特殊注解。

虽然在前文提到过 Bean 的后处理器可以处理@Aurowired注解,但在这里还是需要再次说明和演示。

注解处理

有这样的类型:

static class Bean1 {
}static class Bean2 {
}@Getter
@ToString
static class Bean3 {private Bean1 bean1;private Bean2 bean2;private String javaHome;@Autowiredpublic void setBean1(Bean1 bean1) {System.out.println("setBean1() is called.");this.bean1 = bean1;}@Resourcepublic void setBean2(Bean2 bean2) {System.out.println("setBean2() is called.");this.bean2 = bean2;}@Autowiredpublic void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {System.out.println("setJavaHome() is called.");this.javaHome = javaHome;}@PostConstructpublic void postConstruct() {System.out.println("postConstruct() is called.");}@PreDestroypublic void preDestroy() {System.out.println("preDestroy() is called.");}
}

Bean3中分别通过@Autowired@Resource注解用方法注入了两个属性,javaHome属性也是利用@Autowired方法注入,不同的是其来源不是 Bean,而是环境变量字符串,所以使用@Value注解。此外,还用两个方法是通过注解定义的 Bean 的生命周期钩子。

使用容器加载这些 Bean 定义并获取实例:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();
Bean3 bean3 = context.getBean(Bean3.class);
System.out.println(bean3);
// 关闭容器
context.close();

这里使用GenericApplicationContext而不是DefaultListableBeanFactory,原因在于前者会自动调用工厂的后处理器并注册 bean 后处理器。

执行代码可以看到,输出中不包括任何依赖注入方法调用或生命周期钩子调用。这表示容器并没有正确处理@Autowired@Resource注解。

@Autowired

我们已经知道,这些 Bean 的注解是由 Bean 的后处理器处理的,因此这里添加处理@Autowired注解的后处理器:

context.registerBean("bean3", Bean3.class);
// 添加处理 @Autowired 注解的后处理器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

执行代码会报错,因为这里有一个需要从环境变量解析的依赖注入,因此还需要为容器添加一个解析器:

// 添加处理 @Autowired 注解的后处理器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 添加处理 @Value 注解的从环境变量、配置中解析字符串的解析器
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

再执行就能观察到 @Autowired@Value注解已经被正确处理。

@Resource

现在还有@Resource注入和生命周期钩子注解没有被处理,同样的,加入相应的 Bean 后处理器:

// 添加处理 @Resource 注解和生命周期钩子注解的后处理器
context.registerBean(CommonAnnotationBeanPostProcessor.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

再执行就能看到所有依赖了注入和生命周期钩子都正常工作了。

@ConfigurationProperties

假设有一个类使用@ConfigurationProperties注解利用配置信息注入属性:

@ConfigurationProperties(prefix = "myapp")
@ToString
@Setter
static class Bean4{private String home;private String version;
}

配置文件:

myapp.home=D://home
myapp.version=21

要让这个注解生效,就需要使用新的后处理器:

// 添加处理 @ConfigurationProperties 注解的后处理器
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

添加方式略有不同,是因为除了添加 Bean 定义,这个注解处理器还需要绑定属性等。

现在执行不会得到想要的效果,这是因为容器并没有读取配置文件并加载配置信息。要手动加载:

// 1. 创建属性源
org.springframework.core.io.Resource resource = new ClassPathResource("application.properties");
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 2. 将属性添加到 Environment
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
propertySources.addFirst(new PropertiesPropertySource("appConfig", properties));
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

工作原理

示例类型如下:

static class Bean1 {
}static class Bean2 {
}@ToString
static class Bean3 {@Autowiredprivate Bean1 bean1;private Bean2 bean2;private String javaHome;@Autowiredpublic void setBean2(Bean2 bean2) {this.bean2 = bean2;}@Autowiredpublic void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {this.javaHome = javaHome;}
}

Bean3中分别通过@Autowired注解进行了属性注入、方法注入和用环境变量方法注入。

下面展示实现这些注入的原理。

首先,看最外层的后处理器调用实现:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("bean1", new Bean1());
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
// 关联 bean 工厂
Bean3 bean3 = new Bean3();
beanPostProcessor.setBeanFactory(beanFactory);
// 模拟 bean 的后处理器调用
beanPostProcessor.postProcessProperties(null, bean3, "bean3");
System.out.println(bean3);

输出结果中的环境变量表达式没有被正确处理(javaHome=${JAVA_HOME}),因此需要添加一个解析器:

beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加对`${...}`表达式的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

查看源码会发现,postProcessProperties方法中主要调用了一个私有方法:

image-20250620164622998

其,返回类型为InjectionMetadata,这个方法的用途是检索目标 Bean 定义,将查找到的需要注入的属性/方法记录到InjectionMetadata对象中:

image-20250620164810318

可以通过反射模拟该方法调用:

// 模拟 bean 的后处理器调用
Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
findAutowiringMetadata.setAccessible(true);
InjectionMetadata metadata = (InjectionMetadata)findAutowiringMetadata.invoke(beanPostProcessor, "bean3", Bean3.class, null);
metadata.inject(bean3, "bean3", null);
System.out.println(bean3);

现在已经知道哪些地方需要注入了,就可以通过反射调用来模拟注入过程。

模拟字段注入:

// 模拟属性注入
Field bean1Field = Bean3.class.getDeclaredField("bean1");
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(bean1Field, true);
Bean1 bean1 = (Bean1) beanFactory.doResolveDependency(dependencyDescriptor, null, null, null);
bean1Field.set(bean3, bean1);

模拟方法注入:

// 模拟方法注入
Method setBean2 = Bean3.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
Bean2 bean2 = (Bean2) beanFactory.doResolveDependency(dependencyDescriptor, null, null, null);
setBean2.invoke(bean3, bean2);

模拟方法注入环境变量:

// 模拟 方法注入 Value
Method setJavaHome = Bean3.class.getDeclaredMethod("setJavaHome", String.class);
String javaHome = (String) beanFactory.doResolveDependency(new DependencyDescriptor(new MethodParameter(setJavaHome, 0), true), null, null, null);
setJavaHome.invoke(bean3, javaHome);

本文的完整示例代码可以从这里获取。

The End.

相关文章:

  • 大模型学习入门——Day3:注意力机制
  • 零基础入门PCB设计 一实践项目篇 第四章(STM32开发板PCB设计)
  • Seata:微服务分布式事务的解决方案
  • 饼图:数据可视化的“切蛋糕”艺术
  • 详解Redis的过期策略
  • 邮件合并----批量从excel表中导出数据到word中
  • Git——分布式版本控制工具
  • 【ESP32摄像头开发实例】-实现遥控视频小车
  • 让Agent的应用价值增长
  • 热点Key拆分方案实现
  • Excel批量计算时间差
  • 前端 CSS 框架:分类、选择与应用
  • tkinter 的 grid() 布局管理器学习指南
  • GM DC Monitor v2.0 卸载教程
  • lingma(阿里云Ai)结合idea使用
  • 【量化】策略交易之相对强弱指数策略(RSI)
  • HCIP-华为VRP系统基础
  • 3.读取图片和图片采集
  • C#中的QUIC实现
  • 多协议物联网关的方案测试-基于米尔全志T536开发板
  • 网站设计思路怎么写/视频外链在线生成
  • 互联网网站制作公司/360搜索引擎优化
  • 长沙高端网站制作公司/google中文搜索引擎
  • 电脑编程与网站建设/佛山seo整站优化
  • 武汉网站整合营销联系方式/上海网站seo策划
  • 做网站报价公司/网页制作免费网站制作