代理模式
深入解析代理模式:概念、实现与应用场景
在软件开发中,当需要为对象提供额外功能(如权限控制、日志记录、缓存处理),或需隐藏对象真实实现、控制对象访问时,代理模式(Proxy Pattern)是高效解决方案。它通过引入 “代理对象” 作为中间层,间接访问目标对象,在不修改目标对象代码的前提下扩展其功能,完美符合 “开闭原则”。本文将从核心概念出发,详解静态代理与动态代理的实现方式,结合实际场景分析其价值。
一、代理模式的核心概念
代理模式的本质是 **“代理对象” 替代 “目标对象” 完成交互,并附加额外逻辑 **。其核心是分离 “核心业务逻辑” 与 “非核心辅助逻辑”(如日志、权限),确保目标对象专注于核心功能。模式中包含三类关键角色,职责划分明确:
1. 抽象主题(Subject)
-
定义:声明目标对象与代理对象的共同接口,通常为接口或抽象类。
-
职责:规范核心业务方法,确保代理对象与目标对象具有一致的对外接口,客户端可无差别调用(即 “面向接口编程”)。
-
示例:
UserService接口,包含login(String username, String password)等核心业务方法。
2. 目标对象(Real Subject)
-
定义:实现抽象主题接口,包含真正的核心业务逻辑(如用户登录验证、数据查询)。
-
职责:专注于自身核心功能,不关心非核心逻辑(如登录日志记录),避免代码冗余。
-
示例:
UserServiceImpl类,实现UserService接口,在login方法中完成账号密码校验。
3. 代理对象(Proxy)
-
定义:同样实现抽象主题接口,内部持有目标对象的引用。
-
职责:
-
接收客户端请求,调用目标对象的核心方法;
-
在调用前后附加非核心逻辑(如 “登录前记录请求参数”“登录后记录结果日志”);
-
控制目标对象的访问(如 “未授权用户禁止调用某些方法”)。
- 示例:
UserServiceProxy类,持有UserService实例,在login方法中添加日志记录逻辑。
二、代理模式的两种核心实现方式
代理模式根据 “代理对象创建时机” 的不同,分为静态代理(编译期创建代理类)和动态代理(运行期动态生成代理类),二者适用场景与实现复杂度差异显著。
1. 静态代理(Static Proxy)
静态代理是最简单的实现方式,代理类需手动编写,在编译期即确定代理关系,适合代理类数量少、逻辑简单的场景。
实现示例:用户登录日志代理
以 “用户登录时记录请求日志” 为例,展示静态代理的完整流程:
(1)抽象主题(UserService 接口)
// 抽象主题:用户服务接口,定义核心业务方法
public interface UserService {
// 核心功能:用户登录
boolean login(String username, String password);
// 核心功能:获取用户信息
String getUserInfo(String username);
}
(2)目标对象(UserServiceImpl 类)
// 目标对象:实现用户服务接口,专注核心业务
public class UserServiceImpl implements UserService {
@Override
public boolean login(String username, String password) {
// 核心逻辑:模拟账号密码校验(实际项目中可能查询数据库)
System.out.println("[核心业务] 正在校验用户:" + username);
return "admin".equals(username) && "123456".equals(password);
}
@Override
public String getUserInfo(String username) {
// 核心逻辑:模拟查询用户信息
System.out.println("[核心业务] 正在查询用户:" + username + " 的信息");
return "用户:" + username + ",角色:管理员,状态:正常";
}
}
(3)代理对象(UserServiceProxy 类)
// 代理对象:实现用户服务接口,附加日志记录逻辑
public class UserServiceProxy implements UserService {
// 持有目标对象的引用(通过构造器注入)
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public boolean login(String username, String password) {
// 代理逻辑1:调用目标方法前,记录请求日志
System.out.println("[代理日志] 接收到登录请求:username=" + username + ",password=***");
// 调用目标对象的核心方法
boolean loginResult = target.login(username, password);
// 代理逻辑2:调用目标方法后,记录结果日志
System.out.println("[代理日志] 登录请求处理完成,结果:" + (loginResult ? "成功" : "失败"));
return loginResult;
}
@Override
public String getUserInfo(String username) {
// 代理逻辑:统一添加日志(与login方法逻辑类似,可抽取为通用方法)
System.out.println("[代理日志] 接收到查询请求:username=" + username);
String userInfo = target.getUserInfo(username);
System.out.println("[代理日志] 查询请求处理完成,结果:" + userInfo);
return userInfo;
}
}
(4)客户端调用
// 客户端:通过代理对象访问目标对象,无需直接依赖目标类
public class StaticProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
UserService userService = new UserServiceImpl();
// 2. 创建代理对象,注入目标对象
UserService proxy = new UserServiceProxy(userService);
// 3. 调用代理对象的方法(间接调用目标对象方法,并附加日志)
System.out.println("=== 测试登录功能 ===");
boolean loginSuccess = proxy.login("admin", "123456");
System.out.println("n=== 测试查询用户信息功能 ===");
if (loginSuccess) {
String userInfo = proxy.getUserInfo("admin");
}
}
}
(5)输出结果
=== 测试登录功能 ===
[代理日志] 接收到登录请求:username=admin,password=***
[核心业务] 正在校验用户:admin
[代理日志] 登录请求处理完成,结果:成功
=== 测试查询用户信息功能 ===
[代理日志] 接收到查询请求:username=admin
[核心业务] 正在查询用户:admin 的信息
[代理日志] 查询请求处理完成,结果:用户:admin,角色:管理员,状态:正常
2. 动态代理(Dynamic Proxy)
静态代理的缺点明显:每新增一个目标类,需对应编写一个代理类(如OrderService需OrderServiceProxy),导致类数量膨胀。动态代理可在运行期动态生成代理类,无需手动编写,大幅减少代码冗余,是企业级开发的主流选择。
Java 中动态代理主要有两种实现方式:JDK 动态代理(基于接口,需目标类实现接口)和CGLIB 动态代理(基于继承,无需目标类实现接口)。
(1)JDK 动态代理(基于接口)
JDK 动态代理由java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler实现,核心是通过 “调用处理器” 统一处理所有代理方法的增强逻辑。
实现示例:通用日志代理处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 通用代理处理器:实现InvocationHandler,统一处理代理逻辑(如日志)
public class LogInvocationHandler implements InvocationHandler {
// 持有目标对象的引用(通用类型,可适配任意目标类)
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 核心方法:所有代理对象的方法调用,都会转发到invoke方法
* @param proxy 代理对象本身(一般不用)
* @param method 被调用的目标方法
* @param args 方法参数
* @return 目标方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理逻辑1:调用前记录日志(通用逻辑,适配所有方法)
System.out.println("[动态代理日志] 方法 " + method.getName() + " 开始调用,参数:" + (args != null ? java.util.Arrays.toString(args) : "无"));
// 调用目标对象的核心方法(通过反射执行)
Object result = method.invoke(target, args);
// 代理逻辑2:调用后记录日志
System.out.println("[动态代理日志] 方法 " + method.getName() + " 调用完成,返回值:" + result);
return result;
}
/**
* 工厂方法:创建代理对象(简化客户端调用)
*/
public Object getProxy() {
// Proxy.newProxyInstance参数说明:
// 1. 目标对象的类加载器(确保代理类与目标类在同一类加载器下)
// 2. 目标对象实现的所有接口(JDK动态代理基于接口)
// 3. 调用处理器(代理逻辑的核心)
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
}
客户端调用(适配任意目标类)
// 客户端:通过动态代理处理器创建代理对象,适配UserService、OrderService等任意接口类
public class JdkDynamicProxyTest {
public static void main(String[] args) {
// 1. 测试UserService代理
System.out.println("=== 测试UserService动态代理 ===");
UserService userService = new UserServiceImpl();
// 创建代理处理器,注入UserService目标对象
LogInvocationHandler userProxyHandler = new LogInvocationHandler(userService);
// 获取代理对象(需强转为接口类型,因为JDK代理生成的类实现了目标接口)
UserService userProxy = (UserService) userProxyHandler.getProxy();
// 调用代理方法
userProxy.login("admin", "123456");
userProxy.getUserInfo("admin");
// 2. 扩展测试:若有OrderService,同样可使用该处理器(无需新增代理类)
System.out.println("n=== 测试OrderService动态代理 ===");
OrderService orderService = new OrderServiceImpl();
LogInvocationHandler orderProxyHandler = new LogInvocationHandler(orderService);
OrderService orderProxy = (OrderService) orderProxyHandler.getProxy();
orderProxy.createOrder("admin", "1001"); // 调用OrderService方法,同样附加日志
}
// 新增:模拟订单服务接口(用于演示动态代理的通用性)
interface OrderService {
boolean createOrder(String username, String orderId);
}
// 新增:订单服务实现类(目标对象)
static class OrderServiceImpl implements OrderService {
@Override
public boolean createOrder(String username, String orderId) {
System.out.println("[核心业务] 为用户 " + username + " 创建订单:" + orderId);
return true;
}
}
}
输出结果
=== 测试UserService动态代理 ===
[动态代理日志] 方法 login 开始调用,参数:[admin, 123456]
[核心业务] 正在校验用户:admin
[动态代理日志] 方法 login 调用完成,返回值:true
[动态代理日志] 方法 getUserInfo 开始调用,参数:[admin]
[核心业务] 正在查询用户:admin 的信息
[动态代理日志] 方法 getUserInfo 调用完成,返回值:用户:admin,角色:管理员,状态:正常
=== 测试OrderService动态代理 ===
[动态代理日志] 方法 createOrder 开始调用,参数:[admin, 1001]
[核心业务] 为用户 admin 创建订单:1001
[动态代理日志] 方法 createOrder 调用完成,返回值:true
(2)CGLIB 动态代理(基于继承)
JDK 动态代理要求目标类必须实现接口,若目标类无接口(如UserDao普通类),则需使用 CGLIB 动态代理。CGLIB(Code Generation Library)通过 “继承目标类” 生成代理子类,在子类中重写目标方法并附加增强逻辑,需引入第三方依赖(Maven/Gradle)。
1. 引入 CGLIB 依赖(Maven)
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version> <!-- 最新版本可查询Maven仓库 -->
</dependency>
2. 实现 CGLIB 代理处理器
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// CGLIB代理处理器:实现MethodInterceptor,类似JDK的InvocationHandler
public class CglibLogInterceptor implements MethodInterceptor {
// 持有目标对象的引用(通用类型)
private final Object target;
public CglibLogInterceptor(Object target) {
this.target = target;
}
/**
* 核心方法:所有代理对象的方法调用,都会转发到intercept方法
* @param obj 代理对象(子类实例)
* @param method 被调用的目标方法(父类方法)
* @param args 方法参数
* @param proxy 方法代理对象(用于调用父类方法,避免递归)
* @return 目标方法的返回值
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 代理逻辑1:调用前记录日志
System.out.println("[CGLIB动态代理日志] 方法 " + method.getName() + " 开始调用,参数:" + (args != null ? java.util.Arrays.toString(args) : "无"));
// 调用目标对象的核心方法(通过proxy.invokeSuper,避免调用代理对象方法导致递归)
Object result = proxy.invokeSuper(obj, args);
// 代理逻辑2:调用后记录日志
System.out.println("[CGLIB动态代理日志] 方法 " + method.getName() + " 调用完成,返回值:" + result);
return result;
}
/**
* 工厂方法:创建CGLIB代理对象
*/
public Object getProxy() {
// Enhancer是CGLIB的核心类,用于生成代理子类
Enhancer enhancer = new Enhancer();
// 设置父类(目标类):代理类将继承该类
enhancer.setSuperclass(target.getClass());
// 设置回调处理器:代理逻辑的核心
enhancer.setCallback(this);
// 生成并返回代理对象(子类实例)
return enhancer.create();
}
}
3. 客户端调用(目标类无接口)
// 客户端:为无接口的目标类创建CGLIB代理
public class CglibDynamicProxyTest {
public static void main(String[] args) {
// 1. 创建无接口的目标对象(如UserDao,仅普通类)
UserDao userDao = new UserDao();
// 2. 创建CGLIB代理处理器,注入目标对象
CglibLogInterceptor proxyInterceptor = new CglibLogInterceptor(userDao);
// 3. 获取代理对象(无需强转为接口,直接是目标类的子类)
UserDao userDaoProxy = (UserDao) proxyInterceptor.getProxy();
// 4. 调用代理对象的方法(重写的父类方法,附加日志)
System.out.println("=== 测试CGLIB动态代理 ===");
userDaoProxy.queryUserCount();
userDaoProxy.updateUserStatus("admin", "正常");
}
// 无接口的目标类:普通Java类(无法使用JDK动态代理)
static class UserDao {
// 普通方法1:查询用户数量
public int queryUserCount() {
System.out.println("[核心业务] 查询用户总数");
return 100; // 模拟返回100个用户
}
// 普通方法2:更新用户状态
public boolean updateUserStatus(String username, String status) {
System.out.println("[核心业务] 更新用户 " + username + " 状态为:" + status);
return true;
}
}
}
4. 输出结果
=== 测试CGLIB动态代理 ===
[CGLIB动态代理日志] 方法 queryUserCount 开始调用,参数:无
[核心业务] 查询用户总数
[CGLIB动态代理日志] 方法 queryUserCount 调用完成,返回值:100
[CGLIB动态代理日志] 方法 updateUserStatus 开始调用,参数:[admin, 正常]
[核心业务] 更新用户 admin 状态为:正常
[CGLIB动态代理日志] 方法 updateUserStatus 调用完成,返回值:true
三、静态代理与动态代理的对比
| 对比维度 | 静态代理 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|---|
| 实现基础 | 手动编写代理类,实现目标接口 | 基于接口,运行期生成代理类(实现接口) | 基于继承,运行期生成代理子类 |
| 目标类要求 | 需实现接口(或代理类与目标类同父类) | 必须实现接口 | 无接口要求(但不能是 final 类 / 方法) |
| 类数量 | 代理类与目标类一一对应,数量多 | 一个处理器适配所有接口类,无冗余 | 一个处理器适配所有普通类,无冗余 |
| 灵活性 | 低(新增目标类需新增代理类) | 中(仅支持接口类) | 高(支持所有非 final 类) |
| 性能 | 高(编译期确定,无反射开销) | 中(反射调用目标方法,有少量开销) | 低(继承 + ASM 字节码生成,初始化开销大) |
| 适用场景 | 代理类少、逻辑简单(如测试场景) | 企业级开发中接口类的代理(如 Spring AOP) | 无接口类的代理(如遗留系统普通类) |
四、代理模式的核心优势与适用场景
1. 核心优势
-
解耦核心与非核心逻辑:目标对象专注核心业务(如登录校验),代理对象处理非核心逻辑(如日志、权限),代码职责单一,易维护。
-
无侵入扩展功能:无需修改目标对象代码,通过代理即可附加新功能(如给老系统添加监控),符合 “开闭原则”。
-
控制对象访问:代理可拦截非法请求(如未登录用户调用
getUserInfo),实现权限控制、限流等功能。 -
隐藏真实实现:客户端仅与代理交互,无需知道目标对象的存在(如远程代理中,客户端无需关心服务端细节)。
2. 典型适用场景
-
日志记录:在方法调用前后记录请求参数、返回值、耗时(如接口调用日志)。
-
权限控制:校验用户是否有权限调用方法(如管理员才能调用
deleteUser)。 -
缓存处理:对查询方法结果缓存,避免重复查询(如
getUserInfo缓存用户信息)。 -
远程代理:客户端通过本地代理访问远程服务(如 RPC 框架中,代理对象处理网络通信)。
-
延迟加载:延迟初始化目标对象,提升系统启动速度(如重量级对象
UserService,仅在首次调用时创建)。 -
Spring AOP 底层:Spring AOP 基于动态代理实现(默认 JDK 动态代理,无接口时用 CGLIB),通过代理为 Bean 附加事务、日志等切面逻辑。
五、代理模式的注意事项
-
避免过度代理:若仅需简单扩展功能(如单方法日志),直接在目标类中添加逻辑可能更简洁,无需引入代理模式增加复杂度。
-
动态代理的性能问题:JDK 动态代理的反射调用、CGLIB 的字节码生成均有性能开销,高频调用场景需谨慎(可通过缓存代理对象减少初始化开销)。
-
CGLIB 的限制:CGLIB 无法代理
final类或final方法(子类无法继承重写),若目标类含final修饰,需改用 JDK 动态代理或调整类设计。 -
代理链管理:若存在多层代理(如 “日志代理→权限代理→目标对象”),需明确代理顺序,避免逻辑混乱(如先校验权限,再记录日志)。
六、总结
代理模式是 “分层思想” 在设计模式中的典型体现,通过引入代理对象作为中间层,实现了核心业务与辅助功能的解耦。静态代理简单直观,适合小规模场景;动态代理(JDK/CGLIB)灵活高效,是企业级开发的主流选择,尤其在框架底层(如 Spring AOP、RPC)中应用广泛。
掌握代理模式的关键在于理解 “代理对象的中间层作用”—— 它既不替代目标对象的核心功能,也不修改其代码,而是通过 “拦截调用、附加逻辑” 的方式扩展功能。在实际开发中,需根据目标类是否有接口、性能要求、扩展性需求,选择合适的代理实现方式,让代码既简洁又具备弹性。

浙公网安备 33010602011771号