代理模式
代理模式(Proxy Pattern):控制对象访问的中间层
代理模式是结构型设计模式的一种,核心思想是为目标对象提供一个代理对象,通过代理对象控制对目标对象的访问。代理模式在不修改目标对象的前提下,可对其功能进行扩展(如增强、限制、监控等),广泛应用于远程调用、权限控制、日志记录等场景。
代理模式的核心结构

代理模式包含三个核心角色,通过分工实现对目标对象的访问控制:
- 抽象角色(Subject):
声明目标对象和代理对象的共同接口(或抽象类),确保代理对象可替代目标对象。 - 真实角色(Real Subject):
被代理的目标对象,负责实现核心业务逻辑。 - 代理角色(Proxy):
实现抽象角色,内部持有真实角色的引用,负责控制对真实角色的访问,并可在调用前后添加额外操作(如日志、权限校验)。
静态代理(Static Proxy)
静态代理是最简单的代理模式实现,代理类在编译期就已确定,与目标类实现相同的接口。
实现方式
- 定义抽象接口(抽象角色)。
- 真实角色实现接口,提供核心业务逻辑。
- 代理角色实现同一接口,内部持有真实角色的引用,在调用真实角色方法前后添加扩展逻辑。
代码示例
以 “服装工厂代理销售” 为例:
// 1. 抽象角色(接口)
interface ClothFactory {
void produceCloth(); // 生产服装
}
// 2. 真实角色(被代理的工厂)
class NikeFactory implements ClothFactory {
public void produceCloth() {
System.out.println("Nike工厂生产运动鞋和T恤");
}
}
// 3. 代理角色(代理商)
class ProxyFactory implements ClothFactory {
private ClothFactory target; // 持有真实角色的引用
// 通过构造方法注入目标对象
public ProxyFactory(ClothFactory target) {
this.target = target;
}
public void produceCloth() {
// 代理增强:调用前操作
System.out.println("代理商:检查工厂资质,准备生产");
// 调用目标对象的核心方法
target.produceCloth();
// 代理增强:调用后操作
System.out.println("代理商:验收产品,安排销售");
}
}
// 客户端使用
public class StaticProxyDemo {
public static void main(String[] args) {
// 创建真实对象
ClothFactory nike = new NikeFactory();
// 创建代理对象,关联真实对象
ClothFactory proxy = new ProxyFactory(nike);
// 通过代理对象访问目标对象
proxy.produceCloth();
}
}
输出结果
代理商:检查工厂资质,准备生产
Nike工厂生产运动鞋和T恤
代理商:验收产品,安排销售
优缺点
优点
- 简单直观,代理逻辑清晰,易于理解。
- 不修改目标对象,通过代理扩展功能,符合开闭原则。
缺点
- 代码冗余:代理类与目标类必须实现同一接口,若接口方法增多,两者需同步修改。
- 维护成本高:每增加一个目标类,需对应创建一个代理类,类数量膨胀。
动态代理(Dynamic Proxy)
动态代理解决了静态代理的缺陷,代理类在运行时动态生成,无需手动编写代理类代码。常见实现方式有两种:JDK 动态代理和 CGLIB 动态代理。
1. JDK 动态代理(基于接口)
JDK 动态代理是 Java 原生支持的代理方式,要求目标类必须实现接口,底层通过反射机制生成代理类。
核心组件
java.lang.reflect.Proxy:生成代理对象的工具类,核心方法newProxyInstance()用于动态创建代理实例。java.lang.reflect.InvocationHandler:接口,定义代理逻辑(如增强操作),代理对象的所有方法调用都会转发到其invoke()方法。
实现步骤
- 定义抽象接口(同静态代理)。
- 真实角色实现接口。
- 实现
InvocationHandler接口,在invoke()方法中定义代理逻辑(调用目标方法前后的增强操作)。 - 通过
Proxy.newProxyInstance()生成代理对象,关联目标对象和InvocationHandler。
代码示例
// 1. 抽象角色(接口)
interface Subject {
void doAction();
}
// 2. 真实角色
class RealSubject implements Subject {
public void doAction() {
System.out.println("真实对象:执行核心业务逻辑");
}
}
// 3. 实现InvocationHandler,定义代理逻辑
class MyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象(真实角色)
// 绑定目标对象,返回代理对象
public Object bind(Object target) {
this.target = target;
// 生成代理对象:参数为类加载器、目标类实现的接口、当前InvocationHandler
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
/**
* 代理方法的核心逻辑
* @param proxy 生成的代理对象(一般不使用)
* @param method 被调用的目标方法
* @param args 方法参数
* @return 目标方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理:调用前增强(如日志记录)");
// 调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("JDK代理:调用后增强(如结果缓存)");
return result;
}
}
// 客户端使用
public class JdkDynamicProxyDemo {
public static void main(String[] args) {
// 创建真实对象
Subject realSubject = new RealSubject();
// 创建InvocationHandler并绑定真实对象
MyInvocationHandler handler = new MyInvocationHandler();
Subject proxy = (Subject) handler.bind(realSubject);
// 通过代理对象调用方法
proxy.doAction();
}
}
输出结果
JDK代理:调用前增强(如日志记录)
真实对象:执行核心业务逻辑
JDK代理:调用后增强(如结果缓存)
2. CGLIB 动态代理(基于继承)
CGLIB(Code Generation Library)是第三方库,无需目标类实现接口,通过生成目标类的子类作为代理类实现代理功能,底层依赖字节码处理框架 ASM 修改字节码。
适用场景
- 目标类未实现接口(弥补 JDK 动态代理的局限)。
- 需对类的非接口方法进行代理。
实现步骤
- 引入 CGLIB 依赖。
- 定义目标类(无需实现接口)。
- 实现
MethodInterceptor接口,在intercept()方法中定义代理逻辑。 - 通过
Enhancer生成代理子类实例。
代码示例
<!-- Maven依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
// 1. 目标类(无需实现接口)
class TargetClass {
public void doSomething() {
System.out.println("目标类:执行具体操作");
}
}
// 2. 实现MethodInterceptor,定义代理逻辑
class MyMethodInterceptor implements MethodInterceptor {
/**
* 代理方法的核心逻辑
* @param obj 代理对象(目标类的子类实例)
* @param method 被调用的目标方法
* @param args 方法参数
* @param proxy 方法代理对象(用于调用目标类的原始方法)
* @return 目标方法的返回值
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB代理:调用前增强(如权限校验)");
// 调用目标类的原始方法(通过代理子类调用父类方法)
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB代理:调用后增强(如资源释放)");
return result;
}
// 创建代理对象
public Object getProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass); // 设置父类(目标类)
enhancer.setCallback(this); // 设置拦截器
return enhancer.create(); // 生成代理子类实例
}
}
// 客户端使用
public class CglibProxyDemo {
public static void main(String[] args) {
MyMethodInterceptor interceptor = new MyMethodInterceptor();
// 生成代理对象(目标类的子类)
TargetClass proxy = (TargetClass) interceptor.getProxy(TargetClass.class);
// 调用代理对象的方法
proxy.doSomething();
}
}
输出结果
CGLIB代理:调用前增强(如权限校验)
目标类:执行具体操作
CGLIB代理:调用后增强(如资源释放)
3. JDK 动态代理与 CGLIB 的对比
| 维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 底层原理 | 基于反射,生成实现接口的代理类 | 基于 ASM,生成目标类的子类 |
| 目标类要求 | 必须实现接口 | 可无接口(但不能是 final 类 / 方法) |
| 性能 | 调用效率较高(反射优化) | 生成代理类耗时,调用效率略低 |
| 灵活性 | 仅代理接口方法 | 可代理所有非 final 方法 |
代理模式的应用场景
- 远程代理
为远程对象(如分布式系统中的服务)提供本地代理,屏蔽网络通信细节(如 RPC 框架中的服务代理)。 - 安全代理
在调用目标对象前进行权限校验,限制未授权访问(如权限管理系统中的资源访问控制)。 - 日志代理
记录方法调用的参数、返回值和耗时,用于调试或审计(如 AOP 中的日志切面)。 - 延迟加载
延迟初始化重量级对象(如大型文件、数据库连接),仅在首次使用时创建,节省资源。 - 缓存代理
对方法调用结果进行缓存,重复调用时直接返回缓存值(如 Redis 缓存的本地代理)。
代理模式的优缺点
优点
- 解耦:代理类与目标类分离,扩展功能无需修改目标类,符合开闭原则。
- 增强灵活性:可在不改变目标逻辑的前提下,动态添加日志、权限、缓存等功能。
- 保护目标对象:通过代理控制访问,隐藏目标对象的实现细节。
缺点
- 性能损耗:代理层的存在会增加方法调用的间接性,可能降低执行效率(尤其是动态代理的反射或字节码操作)。
- 复杂度提升:动态代理的实现(如 CGLIB)涉及字节码或反射,增加了代码理解难度。
总结
代理模式通过引入中间层(代理对象)实现对目标对象的访问控制,分为静态代理和动态代理:
- 静态代理适用于简单场景,实现简单但维护成本高;
- 动态代理(JDK/CGLIB)适用于复杂场景,可动态生成代理类,灵活性更高。
代理模式的核心价值在于 “控制与增强”,在框架开发(如 Spring AOP)、分布式系统、安全控制等领域应用广泛