代理模式速览
代理模式就是在调用方和目标对象之间加一个中间人,以便在不修改目标代码的前提下增加额外行为。以房屋租赁为例,房东只负责租房,中介可以带上带看、签合同这些活儿。
| 角色 | 对应示例 | 作用说明 |
|---|---|---|
| Subject | HouseSubject 接口 | 定义目标对象和代理的共同行为('租房') |
| RealSubject | RealHouseSubject(房东) | 目标对象,实际干活的人 |
| Proxy | HouseProxy(中介) | 代理类,包装目标对象,前后添加增强逻辑 |
静态代理:手工写代理类
静态代理需要提前写好代理类,一个代理对应一个接口。
// 公共接口
public interface HouseSubject {
void rentHouse();
}
// 目标对象(房东)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("房东出租房屋");
}
}
// 代理类(中介)
public class HouseProxy implements HouseSubject {
private HouseSubject target;
public HouseProxy(HouseSubject target) {
this.target = target;
}
@Override
public void rentHouse() {
System.out.println("带看房"); // 前置增强
target.rentHouse();
System.out.println("签合同"); // 后置增强
}
}
// 使用
HouseSubject agent = new HouseProxy(new RealHouseSubject());
agent.rentHouse();
静态代理的局限很明显:一个代理类只能服务于同一接口的目标。如果系统里有几十个需要增强的服务,就要写几十个代理类。所以动态代理才显得必要。
JDK 动态代理:给有接口的类生成代理
JDK 动态代理在运行时动态生成代理对象,核心是 InvocationHandler 和 Proxy。
public class JDKInvocationHandler implements InvocationHandler {
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始代理");
Object result = method.invoke(target, args);
System.out.println("代理结束");
return result;
}
}
// 调用方式
HouseSubject realLandlord = new RealHouseSubject();
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
realLandlord.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(realLandlord));
proxy.rentHouse();
newProxyInstance 需要三个参数:类加载器、接口数组、InvocationHandler 实例。它的缺点同样明确——只能代理实现了接口的类。
CGLIB 动态代理:没有接口也能代理
CGLIB 通过生成目标类的子类来实现增强,核心是 MethodInterceptor 和 Enhancer。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLIBInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始代理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("代理结束");
return result;
}
}
// 使用
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealHouseSubject.class);
enhancer.setCallback(new CGLIBInterceptor());
RealHouseSubject proxy = (RealHouseSubject) enhancer.create();
proxy.rentHouse();
不需要任何接口,对于普通的 Java 类也能生成代理。
JDK 与 CGLIB 对比
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理条件 | 必须实现接口 | 可以代理无接口的类 |
| 核心 API | InvocationHandler + Proxy | MethodInterceptor + Enhancer |
| 底层原理 | 基于接口的动态代理 | 基于子类的动态代理 |
| Spring 中的场景 | 优先用于有接口的 Bean | 兜底方案,或强制使用(如 Spring Boot 2.x 默认) |
Spring AOP 如何封装动态代理
Spring AOP 把底层的动态代理细节封装成切面、切点和通知,你只需关注业务规则。
核心组件
- 切面(
@Aspect):带有@Aspect注解的类,交给 Spring 管理,内部包含增强逻辑。 - 切点(
@Pointcut或 execution 表达式):指定哪些方法需要被增强。 - 通知(
@Before、@After、@Around等):定义增强的具体时机和内容。
执行流程
调用被增强的方法时,实际调用的是 Spring 容器中的代理对象。代理对象按顺序执行相应通知(比如前置日志),再调用目标方法,最后再执行后置或返回通知。对业务代码完全透明。
Spring AOP 源码简析:代理生成
Spring 在 Bean 初始化后通过 AbstractAutoProxyCreator 的子类(例如 AnnotationAwareAspectJAutoProxyCreator)检查是否需要创建代理。核心方法 wrapIfNecessary 最终使用 ProxyFactory 生成代理。
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(targetBean);
// 如果强制使用 CGLIB,或者目标类没有接口,则采用 CGLIB
if (proxyTargetClass) {
proxyFactory.setProxyTargetClass(true);
}
return proxyFactory.getProxy(classLoader);
getProxy 内部会根据条件创建 JdkDynamicAopProxy 或 CglibAopProxy。Spring Boot 2.x 开始,默认将 proxyTargetClass 设为 true,即优先使用 CGLIB。如果你希望改回 JDK 代理,可在配置文件中设置 spring.aop.proxy-target-class=false。
AOP 与拦截器、统一异常处理的分工
如果你的后端系统像一个餐厅,那么:
| 组件 | 角色 | 范围 | 核心作用 |
|---|---|---|---|
| 拦截器 | 大门保安 | HTTP 请求/响应 | 校验 token、接口限流、记录请求日志 |
| 统一异常 | 售后客服 | 全局异常兜底 | @RestControllerAdvice + @ExceptionHandler 统一错误返回 |
| AOP | 万能服务员 | 任意方法层面 | 事务管理、方法耗时统计、细粒度日志 |
什么时候该用 AOP?
- 需要增强 Service 层、Mapper 层的方法——拦截器管不到这些地方。
- 只需针对某些方法(比如带特定注解的方法)做处理,而不是整个 Controller。
- 需要拿到方法参数、返回值等更丰富的上下文。
几个简单的使用原则:
- 管 HTTP 请求/响应 → 用拦截器。
- 统一异常格式 → 用
@ControllerAdvice。 - 方法级别的增强 → 用 AOP(切面 + 切点 + 通知)。
总结
Spring AOP 的底层就是动态代理,JDK 和 CGLIB 两种方式覆盖了有接口和无接口的类。Spring 把代理的创建和增强逻辑的织入封装成声明式的编程模型,你只需写切面规则,不用碰业务代码。理清它和拦截器、统一异常处理的边界,能让你的代码分层更清晰。

