看到这个标题就很兴奋,感觉自己无敌啦,有木有 哈哈;如果说 AOP 是 Spring 给普通开发者准备的"自动挡"功能,那么 BeanPostProcessor (BPP) 就是留给架构师的**"手动改装车间"。**
在这里,你不再是被动的使用者,你是规则的制定者。你可以拦截每一个 Bean 的诞生过程,修改它的基因(属性),给它穿上铠甲(代理),甚至直接"偷梁换柱",用一个完全不同的对象替换它。
- Spring 容器 = 汽车总装厂。
- Bean = 刚下流水线的裸车。
- BeanPostProcessor = "万能改装厂" 。
无论什么车(Service, Controller, Dao),在出厂交付给用户(其他 Bean)之前,必须 经过这个改装厂。- 换引擎:修改 Bean 的属性值(比如根据配置中心动态注入数据库 URL)。
- 装雷达:给 Bean 包裹一层代理,增加监控、日志、权限检查。
- 变飞机:直接返回一个全新的对象(比如把一个接口变成动态代理实现类),原来的车直接报废。
核心机制:BPP 的生命周期双钩子
BeanPostProcessor 接口非常简单,只有两个方法,但威力无穷:(大神的智慧)
public interface BeanPostProcessor {
// 1. 前置处理:实例化后,属性填充后,初始化前(@PostConstruct 之前)
// 用途:修改属性,或者决定是否需要特殊处理
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
// 2. 后置处理:初始化后(@PostConstruct 之后,init-method 之后)
// 用途:返回代理对象,替换原始 Bean(AOP 的核心位置)
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
- 这两个方法可以返回任意对象 。如果你返回了一个新对象,容器中注册的 Bean 就会变成这个新对象(偷梁换柱)。
- 执行顺序:Spring 内部有多个 BPP,它们的执行顺序可以通过
Ordered接口或@Order注解控制。顺序至关重要!
实战案例一:零侵入性能监控(手写迷你 AOP)
痛点 :老板要求给所有 Service 加上耗时统计,但不想引入沉重的 AOP 配置,也不想让业务代码沾上任何监控逻辑。
方案 :自定义注解 @Monitor + BPP 动态代理。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
String value() default "default";
}
@Service
public class OrderService {
@Monitor("createOrder") // 只需加个注解,很干净
public void createOrder() {
System.out.println("Creating order...");
try { Thread.sleep(100); } catch (Exception e) {}
System.out.println("Order created.");
}
}
我们利用 CGLIB 或 JDK 动态代理,在 BPP 中拦截方法调用。
@Component
public class MonitorBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 100; // 确保在 AOP 之前或之后执行,视需求而定
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
Class<?> clazz = bean.getClass();
// 1. 检查类中是否有标记 @Monitor 的方法
boolean hasMonitorMethod = Arrays.stream(clazz.getDeclaredMethods())
.anyMatch(m -> m.isAnnotationPresent(Monitor.class));
if (!hasMonitorMethod) {
return bean; // 无需处理,直接放行
}
// 2. 【偷梁换柱】创建代理对象
// 这里为了简化,使用 JDK 动态代理(假设 Bean 实现了接口)
// 实际生产中常用 CGLIB (ProxyFactory) 以支持类代理
return Proxy.newProxyInstance(
clazz.getClassLoader(),
clazz.getInterfaces(),
(proxy, method, args) -> {
Monitor monitor = method.getAnnotation(Monitor.class);
if (monitor != null) {
long start = System.currentTimeMillis();
try {
return method.invoke(bean, args); // 调用原始目标
} finally {
long cost = System.currentTimeMillis() - start;
System.out.printf("[MONITOR] %s.%s() 耗时: %d ms%n",
clazz.getSimpleName(), method.getName(), cost);
}
} else {
return method.invoke(bean, args);
}
}
);
}
}
- 零侵入:业务代码不需要知道监控的存在。
- 动态性:运行时决定哪些方法需要监控。
- 原理 :这就是 Spring AOP 的简化版!Spring 的
AnnotationAwareAspectJAutoProxyCreator本质上也是一个 BPP,它在postProcessAfterInitialization中扫描切点并返回代理对象。
实战案例二:仿 MyBatis/Feign 动态生成 Bean(接口变实现)
痛点 :如何像 MyBatis 的 @Mapper 或 Feign 的 @FeignClient 那样,只定义一个接口,Spring 容器里却自动有了一个能用的实现类?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcClient {
String url();
}
@RpcClient(url = "http://api.example.com")
public interface UserServiceClient {
User getUserById(Long id);
}
// 注意:没有 UserServiceClientImpl 类!
我们需要一个工厂,告诉 Spring:"别找实现类了,我来动态生成一个"。
public class RpcFactoryBean<T> implements FactoryBean<T> {
private Class<T> interfaceClass;
private String url;
public RpcFactoryBean(Class<T> interfaceClass, String url) {
this.interfaceClass = interfaceClass;
this.url = url;
}
@Override
public T getObject() throws Exception {
// 【核心】返回一个动态代理对象,模拟 RPC 调用
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy, method, args) -> {
System.out.println(">>> 发起 RPC 请求到: " + url + "/" + method.getName());
// 模拟返回数据
if (method.getReturnType().equals(User.class)) {
return new User(1L, "DynamicUser");
}
return null;
}
);
}
@Override
public Class<?> getObjectType() {
return interfaceClass;
}
}
4. 核心武器 2:扫描与注册 BPP
这个 BPP 负责扫描接口,发现 @RpcClient 注解,然后动态注册 一个 FactoryBean 到容器中。
@Component
public class RpcClientScannerProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 这一步其实不太对,因为接口本身不会被实例化成 Bean
// 正确的做法通常结合 BeanDefinitionRegistryPostProcessor 在更早阶段扫描
// 但为了演示 BPP 的"替换"能力,我们假设某种机制触发了对接口的处理
// 【修正思路】:通常这类框架使用 BeanDefinitionRegistryPostProcessor
// 在容器刷新前扫描包,直接注册 BeanDefinition。
// 但如果一定要用 BPP 演示"无中生有",通常是配合 @ImportSelector 或特定扫描器。
// 这里我们展示一种特殊情况:如果用户错误地配置了接口为 Bean,我们把它"修好"
if (bean.getClass().isInterface()) {
RpcClient rpcClient = bean.getClass().getAnnotation(RpcClient.class);
if (rpcClient != null) {
// 返回一个工厂产生的代理,替换掉原本可能报错的接口实例
return new RpcFactoryBean<>(bean.getClass(), rpcClient.url()).getObject();
}
}
return bean;
}
}
更专业的实现路径(MyBatis 模式) :
真正的框架(如 MyBatis-Spring)使用的是 BeanDefinitionRegistryPostProcessor。
- 在
postProcessBeanDefinitionRegistry阶段。 - 扫描指定包下的所有接口。
- 为每个接口创建一个
BeanDefinition。 - 设置该 BeanDefinition 的
beanClass为MapperFactoryBean。 - 设置自定义属性(如
mapperInterface)。 - 注册到容器。
这样,容器在后续创建 Bean 时,会自动调用MapperFactoryBean.getObject()生成代理。
实战案例三:上帝之手 ------ 批量修改 Bean 定义 (BD)
痛点 :项目上线前,老板要求把所有 Service 的 Scope 改为 Prototype,或者把所有 Bean 设置为 LazyInit 以优化启动速度。难道要改几千个文件?
方案 :BeanDefinitionRegistryPostProcessor。它在容器刷新最早期执行,此时 Bean 还没出生,连实例化都没开始,只有"蓝图"(BeanDefinition)。