Java代理模式深度解析(静态+动态+Spring AOP底层)
代理模式是结构型设计模式的核心,核心价值是在不修改目标对象源码的前提下,对其功能进行增强或控制访问。它通过引入“代理对象”作为中间层,隔离客户端与目标对象,实现业务分工与功能扩展,也是Spring AOP(面向切面编程)的底层核心原理。本文从静态代理、动态代理(JDK/CGLIB)、应用场景三个维度,结合实战案例详解代理模式,帮你快速掌握其设计思想与落地方式。
一、代理模式的核心原理
1. 设计思想
代理模式的本质是“控制与增强”——代理对象持有目标对象的引用,客户端通过调用代理对象的方法,间接访问目标对象;代理对象在转发请求前后,可添加额外逻辑(如日志、权限校验、事务管理),实现无侵入式扩展。
2. 三大核心角色
代理模式的结构固定,由三个角色构成闭环:
| 角色名称 | 核心职责 |
|---|---|
| Subject(抽象主题) | 定义目标对象与代理对象的共同接口/抽象类,确保代理对象与目标对象类型一致,客户端可统一调用 |
| RealSubject(真实主题) | 被代理的目标对象,实现抽象主题接口,包含核心业务逻辑(如“房东出租房子”) |
| Proxy(代理对象) | 持有真实主题的引用,实现抽象主题接口,转发客户端请求给目标对象,同时添加额外功能(如“中介带看房”) |
3. 核心价值
- 解耦业务逻辑:目标对象专注核心业务(如房东只关注出租),公共功能(如看房、签约)交给代理对象,实现分工;
- 无侵入扩展:不修改目标对象源码,通过代理添加新功能(如日志、缓存、权限),符合开闭原则;
- 控制访问权限:代理对象可拦截客户端请求,实现权限校验、延迟加载、限流等控制逻辑;
- 支撑框架底层:Spring AOP、RPC框架(如Dubbo)、缓存框架等均基于代理模式实现。
二、静态代理(手动实现,简单直观)
静态代理是指代理类在编译期就已生成,与目标对象一一对应,适合目标类少、功能固定的场景。以“租房”为场景,演示静态代理的完整实现。
1. 步骤1:定义抽象主题(Subject)
创建租房的统一接口,规范目标对象(房东)与代理对象(中介)的核心方法:
// 抽象主题:租房接口(定义核心业务方法)
public interface Rent {
/** 核心业务:出租房子 */
void rent();
}
2. 步骤2:实现真实主题(RealSubject)
创建房东类,实现抽象主题接口,专注核心业务逻辑:
// 真实主题:房东(被代理对象,仅关注核心业务)
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东:房屋出租,每月3000元");
}
}
3. 步骤3:实现代理对象(Proxy)
创建中介类,持有房东引用,实现租房接口,在转发请求前后添加额外功能:
// 代理对象:中介(代理房东,添加额外服务)
public class RentProxy implements Rent {
// 持有目标对象(房东)的引用
private Host host;
// 构造器注入目标对象
public RentProxy(Host host) {
this.host = host;
}
@Override
public void rent() {
// 代理前:额外服务1 - 带客户看房
seeHouse();
// 转发请求:调用目标对象的核心业务
host.rent();
// 代理后:额外服务2 - 签订租房合同
signContract();
// 代理后:额外服务3 - 收取中介费
collectFee();
}
// 代理类的额外功能:带看房
private void seeHouse() {
System.out.println("中介:带客户实地看房,介绍房屋详情");
}
// 代理类的额外功能:签订合同
private void signContract() {
System.out.println("中介:协助签订租房合同");
}
// 代理类的额外功能:收取中介费
private void collectFee() {
System.out.println("中介:收取月租金50%的中介费");
}
}
4. 步骤4:客户端调用(Client)
客户端通过代理对象(中介)访问目标对象(房东),无需直接与房东交互:
// 客户端:租房的客户
public class Client {
public static void main(String[] args) {
// 1. 创建目标对象(房东)
Host host = new Host();
// 2. 创建代理对象(中介),关联房东
RentProxy proxy = new RentProxy(host);
// 3. 客户端调用代理对象的方法
proxy.rent();
}
}
5. 运行结果
中介:带客户实地看房,介绍房屋详情
房东:房屋出租,每月3000元
中介:协助签订租房合同
中介:收取月租金50%的中介费
6. 静态代理的优缺点
- 优点:
- 实现简单,逻辑清晰,无需依赖框架;
- 无侵入式扩展,不修改目标对象源码;
- 业务分工明确,目标对象专注核心逻辑。
- 缺点:
- 代码冗余:一个目标对象对应一个代理类,目标类增多时会导致“类爆炸”;
- 维护成本高:目标接口新增方法时,所有代理类需同步修改;
- 灵活性差:代理类编译期固定,无法动态切换代理逻辑。
三、动态代理(运行时生成,灵活高效)
动态代理是指代理类在运行时通过反射机制动态生成,无需手动编写代理类,可适配多个目标对象,是实际开发中的主流方案。Java中动态代理主要分为两种:JDK动态代理(基于接口)和CGLIB动态代理(基于继承)。
1. JDK动态代理(基于接口,JDK原生支持)
核心原理
JDK动态代理通过java.lang.reflect.Proxy类和InvocationHandler接口实现,要求目标对象必须实现接口(代理类动态实现该接口)。
实战案例:通用日志代理
创建一个通用的日志代理,为任意实现接口的目标对象添加日志记录功能:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 1. 通用InvocationHandler:封装代理逻辑(日志记录)
public class LogInvocationHandler implements InvocationHandler {
// 持有目标对象(任意实现接口的对象)
private Object target;
// 注入目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理逻辑核心方法:所有代理对象的方法调用都会转发到这里
* @param proxy 代理对象本身
* @param method 目标对象的方法
* @param args 方法参数
* @return 方法返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理前:记录方法开始日志
System.out.println("=== 日志:方法" + method.getName() + "开始执行 ===");
// 转发请求:调用目标对象的方法
Object result = method.invoke(target, args);
// 代理后:记录方法结束日志
System.out.println("=== 日志:方法" + method.getName() + "执行结束 ===");
return result;
}
// 2. 生成代理对象的工具方法
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象的类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
new LogInvocationHandler(target) // 代理逻辑处理器
);
}
}
// 3. 测试:给租房业务添加日志代理
public class JdkProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象(房东)
Rent host = new Host();
// 2. 动态生成代理对象(日志+租房代理)
Rent proxy = (Rent) LogInvocationHandler.getProxy(host);
// 3. 调用代理对象方法
proxy.rent();
}
}
运行结果
=== 日志:方法rent开始执行 ===
房东:房屋出租,每月3000元
=== 日志:方法rent执行结束 ===
核心特点
- 基于接口实现,目标对象必须实现接口;
- 代理类由JDK动态生成,无需手动编写;
- 灵活通用,一个代理逻辑可适配多个目标对象;
- 底层通过反射实现,性能略低于CGLIB。
2. CGLIB动态代理(基于继承,第三方库)
核心原理
CGLIB(Code Generation Library)是第三方字节码生成库,通过继承目标类动态生成代理类(无需目标类实现接口),底层基于ASM框架修改字节码。Spring AOP默认优先使用JDK动态代理,若目标类无接口则使用CGLIB。
实战案例:给无接口的类添加代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 1. 无接口的目标类(房东)
public class HostNoInterface {
public void rent() {
System.out.println("无接口房东:房屋出租,每月3000元");
}
}
// 2. CGLIB代理逻辑处理器
public class CglibProxy implements MethodInterceptor {
// 持有目标对象
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
// 生成代理对象
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
enhancer.setCallback(this); // 设置代理逻辑处理器
return enhancer.create(); // 生成代理对象
}
/**
* 代理逻辑核心方法
* @param o 代理对象
* @param method 目标方法
* @param args 方法参数
* @param methodProxy 方法代理对象
* @return 方法返回值
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 代理前:带看房
System.out.println("CGLIB代理:带客户看房");
// 转发请求:调用目标对象方法
Object result = method.invoke(target, args);
// 代理后:收取中介费
System.out.println("CGLIB代理:收取中介费");
return result;
}
}
// 3. 测试类
public class CglibProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
HostNoInterface host = new HostNoInterface();
// 2. 生成CGLIB代理对象
HostNoInterface proxy = (HostNoInterface) new CglibProxy(host).getProxy();
// 3. 调用代理对象方法
proxy.rent();
}
}
运行结果
CGLIB代理:带客户看房
无接口房东:房屋出租,每月3000元
CGLIB代理:收取中介费
核心特点
- 基于继承实现,目标对象无需实现接口;
- 代理类是目标类的子类,通过重写方法实现代理;
- 性能优于JDK动态代理(直接操作字节码);
- 无法代理final方法(final方法不能被重写)。
3. 静态代理 vs 动态代理
| 对比维度 | 静态代理 | 动态代理(JDK/CGLIB) |
|---|---|---|
| 代理类生成时机 | 编译期手动编写 | 运行时动态生成 |
| 目标类要求 | 可实现接口,也可无接口 | JDK:需实现接口;CGLIB:无要求 |
| 灵活性 | 差(一个目标类对应一个代理类) | 高(一个代理逻辑适配多个目标类) |
| 维护成本 | 高(接口新增方法需同步修改代理类) | 低(无需修改代理逻辑) |
| 性能 | 高(无反射开销) | JDK:中等;CGLIB:高 |
| 适用场景 | 目标类少、功能固定的简单场景 | 目标类多、功能灵活扩展的复杂场景 |
四、代理模式的实际应用场景
1. 框架底层应用
- Spring AOP:通过动态代理实现切面逻辑(日志、事务、权限),目标对象为业务Bean,代理对象为增强后的Bean;
- RPC框架(Dubbo):客户端通过动态代理生成服务接口的代理对象,实现远程调用的透明化;
- MyBatis:Mapper接口的代理对象由MyBatis动态生成,实现SQL语句的执行;
- 缓存框架(Spring Cache):通过代理对象拦截方法调用,实现缓存的自动存取。
2. 实际开发应用
- 日志记录:代理核心业务方法,记录请求参数、响应结果、执行时间;
- 权限校验:代理需要权限的接口,校验用户是否有权限访问;
- 事务管理:代理数据库操作方法,实现事务的开启、提交、回滚;
- 延迟加载:代理重量级对象(如数据库连接),需要时才初始化;
- 限流熔断:代理高频接口,实现限流(限制请求次数)、熔断(服务故障时降级)。
五、代理模式与装饰器模式的区别(面试高频)
两者均通过“组合”实现功能扩展,容易混淆,核心区别在于设计目标:
| 对比维度 | 代理模式 | 装饰器模式 |
|---|---|---|
| 核心目标 | 控制目标对象的访问(如权限、日志) | 增强目标对象的功能(如添加新服务) |
| 角色定位 | 代理对象与目标对象是“委托关系” | 装饰器与目标对象是“增强关系” |
| 客户端感知 | 客户端通常不知道目标对象的存在 | 客户端明确知道是在增强目标对象 |
| 功能叠加 | 一般单一代理逻辑,不支持多层嵌套 | 支持多层装饰,功能叠加(如咖啡+牛奶+糖) |
| 适用场景 | 访问控制、日志、事务、RPC | 功能动态扩展、组合 |
六、总结
代理模式的核心是“控制与增强”,通过代理对象隔离客户端与目标对象,实现无侵入式扩展:
- 静态代理简单直观,适合简单场景,但灵活性差;
- 动态代理(JDK/CGLIB)灵活高效,是框架与复杂场景的首选;
- 实际开发中,动态代理应用更广(如Spring AOP、RPC),需重点掌握;
- 区分代理模式与装饰器模式的关键:代理侧重“控制访问”,装饰器侧重“增强功能”。
代理模式允许在不修改源代码的情况下对目标对象进行功能扩展。静态代理中,代理类和真实角色是明确的,而动态代理则在运行时生成。文章通过租房的例子展示了静态代理的实现,包括接口、真实角色、代理角色和客户端的交互。代理模式的好处在于解耦和便于扩展公共业务。
224

被折叠的 条评论
为什么被折叠?



