乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

BeanPostProcessor

定位:Spring 容器提供的 Bean 级扩展接口,允许在 Bean 初始化前后插入自定义处理逻辑。它是 Spring AOP 代理生成、日志统一打印、权限校验等机制的核心基础设施。


定义与作用

BeanPostProcessor 是 org.springframework.beans.factory.config 包下的接口,定义如下:

public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

其核心语义是:在 Bean 的初始化阶段前后,对 Bean 实例进行加工或替换。两个方法分别对应:

  • postProcessBeforeInitialization:在初始化回调(@PostConstruct / InitializingBean / init-method)之前执行
  • postProcessAfterInitialization:在初始化回调之后执行,Spring AOP 代理对象在此阶段生成

BeanPostProcessor 是 Spring 框架最强大的扩展点之一,典型应用场景包括:

  • AOP 代理生成:AbstractAutoProxyCreator 在 afterInitialization 阶段为目标 Bean 创建代理
  • 统一日志:打印每个 Bean 的创建耗时和依赖关系
  • 属性校验:在初始化前检查 Bean 的属性是否满足业务规则
  • Bean 包装:将原始 Bean 包装为具有额外功能的代理对象(如事务代理、远程调用代理)

适用位置与常用方法

项目说明
实现方式类实现 BeanPostProcessor 接口,并注册为 Spring Bean
作用范围容器内所有 Bean,除非在方法内部通过 beanName 过滤
执行次数每个 Bean 实例创建时各调用一次(before + after)
返回值可以返回原始 Bean,也可以返回包装后的代理对象
执行时机before:Aware 回调之后、初始化之前;after:初始化之后、Bean 就绪之前

关键特性:BeanPostProcessor 的 postProcessAfterInitialization 返回的对象将替代原始 Bean 注册到容器中。这是 Spring AOP 生成代理对象的核心机制。


核心原理

Spring 容器在 AbstractAutowireCapableBeanFactory.doCreateBean() 中,于属性填充和 Aware 回调完成后,依次调用所有注册的 BeanPostProcessor。

BPP 执行时序图

以下时序图展示了多个 BeanPostProcessor 围绕单个 Bean 初始化的执行顺序:


完整示例:飞翔科技学生管理系统的 Bean 创建日志与权限校验

场景简述

飞翔科技 CTO 大翔在架构评审会上提出两个硬性要求:

  1. 可观测性:系统启动时打印每个 Bean 的创建耗时,方便排查启动慢的问题
  2. 安全性:所有 Service 层 Bean 在初始化后必须检查是否标注了 @Secured 注解,未标注的 Bean 启动即报错

后端开发小崔负责实现这两个需求,架构师白歌建议使用 BeanPostProcessor 统一处理,避免在每个 Service 中重复写日志和校验代码。

操作前:无 BPP 的代码

在没有 BeanPostProcessor 时,小崔只能让每个 Service 自行处理日志和校验,导致代码重复、维护困难:

@Service
public class StudentService {

    private final StudentDao studentDao;

    public StudentService(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @PostConstruct
    public void init() {
        // 每个 Service 都要写重复的日志
        System.out.println("[StudentService] Bean 创建完成");
        // 每个 Service 都要写重复的校验
        if (!this.getClass().isAnnotationPresent(Secured.class)) {
            throw new IllegalStateException("[StudentService] 未标注 @Secured 注解");
        }
    }
}

@Service
public class CourseService {

    private final CourseDao courseDao;

    public CourseService(CourseDao courseDao) {
        this.courseDao = courseDao;
    }

    @PostConstruct
    public void init() {
        // 重复代码!
        System.out.println("[CourseService] Bean 创建完成");
        if (!this.getClass().isAnnotationPresent(Secured.class)) {
            throw new IllegalStateException("[CourseService] 未标注 @Secured 注解");
        }
    }
}

问题分析:

  • 每个 Service 都包含重复的 @PostConstruct 日志和校验逻辑
  • 新增 Service 时容易遗漏校验,存在安全风险
  • 日志格式不统一,难以聚合分析
  • 校验逻辑分散,修改规则时需要改动多个文件

操作后:使用 BeanPostProcessor 的完整代码

小崔重构代码,将日志和校验逻辑提取到两个独立的 BeanPostProcessor 中:

1. 统一日志 BPP

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class BeanCreationLoggingProcessor implements BeanPostProcessor, Ordered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 在初始化前记录开始时间
        if (isTargetBean(bean)) {
            System.out.println("[BPP-Log] >>> 开始初始化 Bean:" + beanName
                + ",类型:" + bean.getClass().getSimpleName());
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 在初始化后记录完成
        if (isTargetBean(bean)) {
            System.out.println("[BPP-Log] <<< 完成初始化 Bean:" + beanName
                + ",类型:" + bean.getClass().getSimpleName());
        }
        return bean;
    }

    private boolean isTargetBean(Object bean) {
        // 只关注 Service 层 Bean
        String className = bean.getClass().getName();
        return className.contains("Service") && !className.contains("Processor");
    }

    @Override
    public int getOrder() {
        // 最高优先级,确保最先执行
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

2. 安全校验 BPP

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Component
public class SecurityValidationProcessor implements BeanPostProcessor, Ordered {

    /**
     * 自定义安全注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Secured {
        String value() default "";
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 校验在初始化前执行,确保非法 Bean 不会进入就绪状态
        if (isServiceBean(bean)) {
            Class<?> clazz = bean.getClass();
            // 处理 CGLIB 代理类,获取原始类
            if (clazz.getName().contains("CGLIB")) {
                clazz = clazz.getSuperclass();
            }

            if (!clazz.isAnnotationPresent(Secured.class)) {
                throw new IllegalStateException(
                    "[BPP-Security] Bean '" + beanName + "' (类型:" + clazz.getSimpleName()
                        + ") 未标注 @Secured 注解,启动被拒绝"
                );
            }
            System.out.println("[BPP-Security] Bean '" + beanName + "' 通过安全校验");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 安全校验已在 before 阶段完成,after 阶段直接返回
        return bean;
    }

    private boolean isServiceBean(Object bean) {
        String className = bean.getClass().getName();
        return className.contains("Service") && !className.contains("Processor");
    }

    @Override
    public int getOrder() {
        // 次于日志处理器,但仍在其他 BPP 之前
        return Ordered.HIGHEST_PRECEDENCE + 100;
    }
}

3. 标注了 @Secured 的 Service

import org.springframework.stereotype.Service;

@Service
@SecurityValidationProcessor.Secured("student_module")
public class StudentService {

    public String getStudentName(Long id) {
        return "学生" + id;
    }
}
import org.springframework.stereotype.Service;

@Service
// 故意不标注 @Secured,用于演示校验失败场景
public class CourseService {

    public String getCourseName(Long id) {
        return "课程" + id;
    }
}

Spring Boot 启动验证(CourseService 未标注 @Secured):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudentManagementApplication.class, args);
    }
}

运行结果及分析:

[BPP-Log] >>> 开始初始化 Bean:studentService,类型:StudentService
[BPP-Security] Bean 'studentService' 通过安全校验
[BPP-Log] <<< 完成初始化 Bean:studentService,类型:StudentService
[BPP-Log] >>> 开始初始化 Bean:courseService,类型:CourseService
Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
  Error creating bean with name 'courseService':
  BeanPostProcessor before instantiation of bean failed;
  nested exception is java.lang.IllegalStateException:
  [BPP-Security] Bean 'courseService' (类型:CourseService) 未标注 @Secured 注解,启动被拒绝

关键差异:

  • 操作前:每个 Service 重复写日志和校验,维护困难,容易遗漏
  • 操作后:日志和校验逻辑集中到 BPP 中,所有 Service 自动生效;未标注 @Secured 的 Bean 在启动阶段即被拒绝,安全策略统一且强制

易错场景与面试考点

易错场景一:BPP 中返回 null 导致 Bean 丢失

@Component
public class BrokenProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof StudentService) {
            // 错误!返回 null 会导致容器认为该 Bean 不应存在
            return null;
        }
        return bean;
    }
}

问题:postProcessBeforeInitialization 返回 null 时,Spring 容器会中断当前 Bean 的后续初始化流程,导致该 Bean 无法正常使用。若确实需要跳过某个 Bean,应返回原始 bean 而非 null。

正确做法:

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof StudentService) {
        // 执行某些操作,但始终返回原始 bean 或其代理
        System.out.println("[BPP] 处理 StudentService");
    }
    return bean; // 绝不返回 null
}

易错场景二:BPP 自身被 BPP 处理导致循环或异常

@Component
public class LoggingProcessor implements BeanPostProcessor {
    @Autowired
    private StudentService studentService; // 错误!BPP 不应依赖其他 Bean

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("[BPP] 创建 Bean:" + beanName);
        return bean;
    }
}

问题:BeanPostProcessor 是容器启动早期实例化的特殊 Bean。若 BPP 中 @Autowired 注入普通 Bean,会导致该普通 Bean 在 BPP 实例化阶段被提前创建,此时其他 BPP 可能尚未注册,造成初始化顺序混乱或属性注入不完整。

正确做法:BPP 中若需访问其他 Bean,使用 BeanFactoryAware 延迟获取,或在 postProcessAfterInitialization 中通过参数传入的 bean 对象操作。

易错场景三:忽略 BPP 的 Ordered 优先级

@Component
public class ProxyCreator implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 生成代理对象
        return createProxy(bean);
    }
}

@Component
public class AttributeSetter implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 尝试在 bean 上设置额外属性
        if (bean instanceof StudentService) {
            // 错误!若 ProxyCreator 先执行,此时 bean 已经是代理对象
            // 代理对象可能不暴露原始对象的 setter 方法
        }
        return bean;
    }
}

问题:若 ProxyCreator 先于 AttributeSetter 执行,AttributeSetter 拿到的是代理对象而非原始对象,对其设置属性可能无效或抛出异常。

正确做法:通过 Ordered 接口或 @Order 注解控制 BPP 执行顺序。生成代理的 BPP 应该最后执行:

@Component
@Order(Ordered.LOWEST_PRECEDENCE) // 确保最后执行
public class ProxyCreator implements BeanPostProcessor {
    // ...
}

面试考点

Q1:BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别?

BeanPostProcessor 在 Bean 实例化之后 对 Bean 实例进行加工,操作的是对象;BeanFactoryPostProcessor 在 BeanDefinition 加载之后、实例化之前 对配置元数据进行修改,操作的是 BeanDefinition。前者影响 Bean 实例,后者影响 Bean 的创建规则。

Q2:Spring AOP 代理是在哪个阶段生成的?

在 BeanPostProcessor.postProcessAfterInitialization() 阶段生成。AbstractAutoProxyCreator 是一个特殊的 BPP,它在 afterInitialization 阶段检查 Bean 是否需要代理,若需要则创建 JDK 动态代理或 CGLIB 代理并返回。

Q3:postProcessAfterInitialization 返回的代理对象,还能被其他 BPP 处理吗?

可以。BPP 链按顺序执行,前一个 BPP 返回的代理对象会作为参数传递给下一个 BPP。但通常生成代理的 BPP 应该配置为最低优先级(最后执行),避免后续 BPP 对代理对象做不必要的加工。

Q4:BeanPostProcessor 能拦截所有 Bean 吗?

几乎能拦截所有 Bean,但不能拦截 BeanPostProcessor 自身。BPP 是在容器启动早期实例化的,它们不会被其他 BPP 处理。若需要对 BPP 做特殊处理,需使用 BeanFactoryPostProcessor。


本文边界说明

本文档仅讲解 BeanPostProcessor 接口。关于 JSR-250 注解 @PostConstruct 和 @PreDestroy、Spring 接口 InitializingBean / DisposableBean、以及容器级扩展点 BeanFactoryPostProcessor,请分别参阅本章其他独立文档。严禁在讲解 BeanPostProcessor 时混入 AOP 切面定义(@Aspect、@Pointcut 等)或 Web 层注解,以保持知识点的原子性和教学清晰度。

上一页
DisposableBean
下一页
BeanFactoryPostProcessor