《Spring Boot 3:高级与架构设计》第 2 章 IOC容器的高级机制 BeanFactoryPostProcessor 个人理解 6

《Spring Boot 3:高级与架构设计》第 2 章 IOC容器的高级机制 BeanFactoryPostProcessor 个人理解 6

1 什么是 BeanFactoryPostProcessor

BeanFactoryPostProcessor 也是一个后置处理器,与 BeanPostProcessor 不同的是,它并不作用于 IOC 容器中的 Bean,而直接作用于 IOC 容器中的 BeanDefinition​。

BeanFactoryPostProcessor 的执行时机是在所有非延迟加载的单实例 Bean 初始化之前。

简而言之,BeanFactoryPostProcessor 的触发时机如图所示,即 BeanDefinition 已经注册到 BeanDefinitionRegistry,但尚未初始化非延迟加载的单实例 Bean。

2 BeanFactoryPostProcessor 如何使用

模拟一个需求场景:

构造几个 Color 颜色类的 Bean,并在这些 Bean 创建之前,将 Bean 的名称写入内部属性。

该需求与实现 BeanNameAware 接口的效果一致。

2.1 回顾 BeanNameAware 接口

BeanNameAware 是一个回调注入接口。

回调注入是一种比较特殊的注入方式,它由 Spring Framework 的扩展机制得来。在实际的项目开发中,如果遇到某些 Bean 需要注入 Spring Framework 的内置组件(如 ApplicationContext)​,则需要利用回调注入来实现。

在较高版本的 Spring Framework 中,使用 @Autowired 即可在大多数普通 Bean 中注入 Spring Framework 的内置组件,但还是有一小部分特殊的 Bean 无法正常使用 @Autowired 注入,该部分内容会在后续的高级篇中讲解。

回调注入机制源自 Spring Framework 3.1 版本,它的核心是一个接口:Aware。Aware 本身内部没有定义任何方法,只是一个类似于Serializable 的标识性接口,但是其下有一系列子接口。

借助 IDEA 观察继承关系,可以发现 Aware 接口的子接口非常多,而且从命名方式能得知这些接口分别是用来注入哪个组件的(如 ApplicationContextAware 注入的组件是 ApplicationContext)​。

常用的子接口绝大多数可以借助 @Autowired 注入,个别需要 Aware 接口来帮忙注入。

下面分别介绍两个相对有代表性的 Aware 子接口的回调注入使用。

1 ApplicationContextAware

ApplicationContextAware 的使用方式非常简单,只需要在需要注入的类中实现该接口,并重写 setApplicationContext 方法即可。AwaredTestBean 中有一个 ApplicationContext 类型的变量 ctx,该变量在 setApplicationContext 方法被回调时赋值。

随后使用组件扫描的方式驱动 IOC 容器,获取 AwaredTestBean 并调用 printBeanNames 方法检验效果。

运行 AwareApplication 的 main 方法,可以发现 IOC 容器中的组件名称被一一打印,证明 ApplicationContext 被成功注入。

java 复制代码
package com.yangjunbo.di.examplej;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

import java.util.stream.Stream;

@Component()
public class AwaredTestBean implements ApplicationContextAware {
    
    private ApplicationContext ctx;
    
    public void printBeanNames() {
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
}
java 复制代码
package com.yangjunbo.di.examplej;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AwareApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.yangjunbo.di.examplej");
        AwaredTestBean bbb = ctx.getBean(AwaredTestBean.class);
        bbb.printBeanNames();
    }
}

2 BeanNameAware

如果当前的 Bean 需要依赖它本身的 Bean 名称,那么使用 @Autowired 和 @Value 将无法注入,此时就需要使用 BeanNameAware 接口来辅助注入当前 Bean 的 Name。

AwaredTestBean 实现 BeanNameAware 接口,并增加 getName 方法用于打印自身的 beanName。

启动类 AwareApplication,添加 AwaredTestBean 的 getName 方法调用并打印。

重新运行 main 方法,可以发现控制台正确打印 "awaredTestBean",证明 BeanNameAware 接口也正常运行。

java 复制代码
package com.yangjunbo.di.examplej;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

import java.util.stream.Stream;

@Component()
public class AwaredTestBean implements ApplicationContextAware, BeanNameAware {

    private String beanName;
    private ApplicationContext ctx;

    public String getName() {
        return beanName;
    }

    public void printBeanNames() {
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}
java 复制代码
package com.yangjunbo.di.examplej;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AwareApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.yangjunbo.di.examplej");
        AwaredTestBean bbb = ctx.getBean(AwaredTestBean.class);
        bbb.printBeanNames();
        bbb.getName();
    }
}

2.2 使用 BeanFactoryPostProcessor 实现需求

1 创建演示环境

新建一个抽象类 Color,创建 Color 的两个子类 Red Green,子类要标注 @Component。

java 复制代码
package com.yangjunbo.spring.postprocessor.exampleh.bean;

public abstract class Color {
    
    protected String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}
java 复制代码
package com.yangjunbo.spring.postprocessor.exampleh.bean;

import org.springframework.stereotype.Component;

@Component
public class Green extends Color {
    
    @Override
    public String toString() {
        return "Green{" + "name='" + name + '\'' + "}";
    }
}
java 复制代码
package com.yangjunbo.spring.postprocessor.exampleh.bean;

import org.springframework.stereotype.Component;

@Component
public class Red extends Color {
    
    @Override
    public String toString() {
        return "Red{" + "name='" + name + '\'' + "}";
    }
}

编写后置处理器。

既然 BeanFactoryPostProcessor 中拿到的参数是 ConfigurableListableBeanFactory,那么就可以操作其 API 取出内部的 BeanDefinition 并进行属性赋值。

由于 ConfigurableListableBeanFactory 无法通过类型取出指定的 BeanDefinitionName,因此只能取出全部 BeanDefinitionName 并逐个判断其 Bean 类型。

此外,由于 BeanDefinition 中只能获取到 beanClassName,无法得知父类,因此还需要借助反射机制检查父类,如代码所示。

java 复制代码
package com.yangjunbo.spring.postprocessor.exampleh.config;

import com.yangjunbo.spring.postprocessor.exampleh.bean.Color;
import org.springframework.beans.BeansException;
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;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.stream.Stream;

@Component
public class ColorNameSetterFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Stream.of(beanFactory.getBeanDefinitionNames()).forEach(beanName -> {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (StringUtils.hasText(beanClassName)) {
                ClassLoader classLoader = this.getClass().getClassLoader();
                Class<?> superclass = ClassUtils.resolveClassName(beanClassName, classLoader).getSuperclass();
                if (superclass.equals(Color.class)) {
                    System.out.println("Color definition name setting ...... " + beanName);
                    beanDefinition.getPropertyValues().add("name", beanName);
                }
            }
        });
    }
}

代码中有几个小细节应注意:不是所有的 BeanDefinition 都有 beanClassName 属性封装,由 1.3.3 节可知,只要 Bean 的注册来源不是通过@Bean 注册,那么就可以获取 beanClassName,而此处使用 @Component 配合组件扫描注册,故可以获取 beanClassName;

获取类型名称后使用工具方法 ClassUtils.resolveClassName 进行反射解析判断,可以避免 ClassNotFoundException 异常。

接下来使用组件扫描加注解驱动 IOC 容器,从中获取 Red 和 Green 并打印,如代码所示。运行 main 方法,控制台打印出 Red 和 Green 的name 属性,证明 ColorNameSetterFactoryPostProcessor 已生效。

java 复制代码
package com.yangjunbo.spring.postprocessor.exampleh;

import com.yangjunbo.spring.postprocessor.exampleh.bean.Green;
import com.yangjunbo.spring.postprocessor.exampleh.bean.Red;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * ClassName: PostProcessorApplication
 * Package: com.yangjunbo.spring.postprocessor.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/8 20:27
 * @Version 1.0
 */
public class PostProcessorHApplication {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(
                "com.yangjunbo.spring.postprocessor.exampleh");
        Red red = ctx.getBean(Red.class);
        System.out.println(red);
        System.out.println(ctx.getBean(Green.class));
    }

}
相关推荐
M78佐菲1 小时前
ARM学习笔记(五)
linux·arm开发·笔记·嵌入式硬件·学习
苏三说技术1 小时前
推荐一个牛逼的AgentScope系统
后端
行百里er1 小时前
5 分钟跑起 Redis(Docker 版)
redis·后端·docker
志尊宝1 小时前
Vue3 零基础每日笔记(052):路由常见坑一次排雷——404、刷新丢参数、部署白屏
前端·javascript·vue.js·笔记·html5
程序员阿鹏1 小时前
简单介绍一下软引用
java·开发语言·jvm·数据结构·后端
回家路上绕了弯1 小时前
如何提升 AI 编码准确性:从“生成代码”到“验证结果”
后端
SL_staff1 小时前
项目延期时如何用四类角色机制实现责任锚定?一线技术实践拆解
java·低代码·团队管理
newerp1 小时前
系统调用与 M/P 解绑
后端·程序员·go
wno7041 小时前
Spring Security Session管理
java·后端·spring