代理模式

深入解析代理模式:概念、实现与应用场景

在软件开发中,当需要为对象提供额外功能(如权限控制、日志记录、缓存处理),或需隐藏对象真实实现、控制对象访问时,代理模式(Proxy Pattern)是高效解决方案。它通过引入 “代理对象” 作为中间层,间接访问目标对象,在不修改目标对象代码的前提下扩展其功能,完美符合 “开闭原则”。本文将从核心概念出发,详解静态代理与动态代理的实现方式,结合实际场景分析其价值。

一、代理模式的核心概念

代理模式的本质是 **“代理对象” 替代 “目标对象” 完成交互,并附加额外逻辑 **。其核心是分离 “核心业务逻辑” 与 “非核心辅助逻辑”(如日志、权限),确保目标对象专注于核心功能。模式中包含三类关键角色,职责划分明确:

1. 抽象主题(Subject)

  • 定义:声明目标对象与代理对象的共同接口,通常为接口或抽象类。

  • 职责:规范核心业务方法,确保代理对象与目标对象具有一致的对外接口,客户端可无差别调用(即 “面向接口编程”)。

  • 示例UserService接口,包含login(String username, String password)等核心业务方法。

2. 目标对象(Real Subject)

  • 定义:实现抽象主题接口,包含真正的核心业务逻辑(如用户登录验证、数据查询)。

  • 职责:专注于自身核心功能,不关心非核心逻辑(如登录日志记录),避免代码冗余。

  • 示例UserServiceImpl类,实现UserService接口,在login方法中完成账号密码校验。

3. 代理对象(Proxy)

  • 定义:同样实现抽象主题接口,内部持有目标对象的引用。

  • 职责

  1. 接收客户端请求,调用目标对象的核心方法;

  2. 在调用前后附加非核心逻辑(如 “登录前记录请求参数”“登录后记录结果日志”);

  3. 控制目标对象的访问(如 “未授权用户禁止调用某些方法”)。

  • 示例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)

静态代理的缺点明显:每新增一个目标类,需对应编写一个代理类(如OrderServiceOrderServiceProxy),导致类数量膨胀。动态代理可在运行期动态生成代理类,无需手动编写,大幅减少代码冗余,是企业级开发的主流选择。

Java 中动态代理主要有两种实现方式:JDK 动态代理(基于接口,需目标类实现接口)和CGLIB 动态代理(基于继承,无需目标类实现接口)。

(1)JDK 动态代理(基于接口)

JDK 动态代理由java.lang.reflect.Proxyjava.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 附加事务、日志等切面逻辑。

五、代理模式的注意事项

  1. 避免过度代理:若仅需简单扩展功能(如单方法日志),直接在目标类中添加逻辑可能更简洁,无需引入代理模式增加复杂度。

  2. 动态代理的性能问题:JDK 动态代理的反射调用、CGLIB 的字节码生成均有性能开销,高频调用场景需谨慎(可通过缓存代理对象减少初始化开销)。

  3. CGLIB 的限制:CGLIB 无法代理final类或final方法(子类无法继承重写),若目标类含final修饰,需改用 JDK 动态代理或调整类设计。

  4. 代理链管理:若存在多层代理(如 “日志代理→权限代理→目标对象”),需明确代理顺序,避免逻辑混乱(如先校验权限,再记录日志)。

六、总结

代理模式是 “分层思想” 在设计模式中的典型体现,通过引入代理对象作为中间层,实现了核心业务与辅助功能的解耦。静态代理简单直观,适合小规模场景;动态代理(JDK/CGLIB)灵活高效,是企业级开发的主流选择,尤其在框架底层(如 Spring AOP、RPC)中应用广泛。

掌握代理模式的关键在于理解 “代理对象的中间层作用”—— 它既不替代目标对象的核心功能,也不修改其代码,而是通过 “拦截调用、附加逻辑” 的方式扩展功能。在实际开发中,需根据目标类是否有接口、性能要求、扩展性需求,选择合适的代理实现方式,让代码既简洁又具备弹性。

posted @ 2025-11-23 21:26  圣祖帝皇  阅读(3)  评论(0)    收藏  举报