Java 代理模式详解

Java 代理模式详解

代理模式是 结构型设计模式 的一种,核心思想是:通过一个 "代理对象" 间接访问目标对象,在不修改目标对象代码的前提下,对目标对象的行为进行增强或控制(如日志记录、权限校验、事务管理等)。

核心角色

代理模式包含 3 个核心角色,缺一不可:

  1. 抽象主题(Subject):定义目标对象和代理对象的共同接口(或抽象类),是代理模式的 "约定",确保代理对象可替代目标对象。

  2. 目标主题(Target):被代理的真实对象,负责执行核心业务逻辑。

  3. 代理主题(Proxy):持有目标对象的引用,实现抽象主题接口,在调用目标对象方法前后,添加增强逻辑(如日志、权限校验)。

代理模式的 3 种实现方式

Java 中代理模式主要分为 静态代理 和 动态代理(JDK 动态代理、CGLIB 动态代理),下面逐一详解。


一、静态代理(Static Proxy)

静态代理是 编译期就生成代理类(手动编写代理类代码),代理类和目标类的关系在编译时确定。

实现步骤

  1. 定义抽象主题接口(Subject);

  2. 实现目标类(Target),重写抽象接口方法;

  3. 实现代理类(Proxy),持有目标对象引用,重写抽象接口方法(调用目标方法 + 增强逻辑)。

代码示例:日志增强

1. 抽象主题接口(Subject)
复制代码
// 抽象主题:定义核心业务方法
public interface UserService {
    void addUser(String username); // 新增用户(核心业务)
}
2. 目标类(Target)
复制代码
// 目标对象:实现核心业务逻辑
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        // 核心业务:新增用户
        System.out.println("核心逻辑:新增用户 -> " + username);
    }
}
3. 代理类(Proxy)
复制代码
// 代理对象:持有目标对象引用,添加增强逻辑(日志记录)
public class UserServiceProxy implements UserService {
    // 持有目标对象的引用
    private final UserService target;
​
    // 构造方法注入目标对象
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
​
    @Override
    public void addUser(String username) {
        // 增强逻辑:方法执行前(日志记录)
        System.out.println("日志:开始执行 addUser 方法,参数:" + username);
        
        // 调用目标对象的核心方法
        target.addUser(username);
        
        // 增强逻辑:方法执行后(日志记录)
        System.out.println("日志:addUser 方法执行完毕\n");
    }
}
4. 测试类
复制代码
public class ProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 2. 创建代理对象(注入目标对象)
        UserService proxy = new UserServiceProxy(target);
        
        // 3. 通过代理对象调用方法(间接访问目标对象)
        proxy.addUser("张三");
        proxy.addUser("李四");
    }
}
输出结果
复制代码
日志:开始执行 addUser 方法,参数:张三
核心逻辑:新增用户 -> 张三
日志:addUser 方法执行完毕
​
日志:开始执行 addUser 方法,参数:李四
核心逻辑:新增用户 -> 李四
日志:addUser 方法执行完毕

静态代理的优缺点

优点 缺点
实现简单,无额外依赖,易理解 代理类与目标类强耦合:目标类新增方法时,代理类需同步修改
增强逻辑清晰,无运行时开销 代码冗余:每个目标类都需对应一个代理类,类数量爆炸
不修改目标对象代码,符合开闭原则 灵活性差:无法动态切换增强逻辑

二、动态代理(Dynamic Proxy)

动态代理是 运行时动态生成代理类(无需手动编写代理类),代理类和目标类的关系在运行时确定,灵活性更高。

Java 中动态代理主要有两种实现:

  • JDK 动态代理:JDK 自带(java.lang.reflect 包),基于接口实现;

  • CGLIB 动态代理:第三方库(需导入依赖),基于继承目标类实现。

1. JDK 动态代理(重点)

核心原理
  • 依赖 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口;

  • 代理类必须实现目标类的 所有接口(JDK 动态代理本质是 "接口代理");

  • 运行时通过 Proxy.newProxyInstance() 动态生成代理对象;

  • 增强逻辑统一写在 InvocationHandler 的 invoke() 方法中。

实现步骤
  1. 定义抽象主题接口(同静态代理);

  2. 实现目标类(同静态代理);

  3. 实现 InvocationHandler 接口,编写增强逻辑;

  4. 通过 Proxy.newProxyInstance() 生成代理对象并调用方法。

代码示例:日志 + 权限增强
1. 抽象主题接口 + 目标类(复用静态代理的 UserService 和 UserServiceImpl)
2. 实现 InvocationHandler(增强逻辑处理器)
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
​
// 增强逻辑处理器:实现 InvocationHandler,统一处理所有代理方法
public class MyInvocationHandler implements InvocationHandler {
    // 持有目标对象的引用
    private final Object target;
​
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
​
    /**
     * 所有代理方法的调用都会转发到 invoke() 方法
     * @param proxy 代理对象(一般不用)
     * @param method 目标方法(被调用的方法)
     * @param args 目标方法的参数
     * @return 目标方法的返回值
     * @throws Throwable 目标方法可能抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强逻辑 1:方法执行前(权限校验)
        System.out.println("权限校验:验证用户是否有权限执行 " + method.getName() + " 方法");
        
        // 增强逻辑 2:方法执行前(日志记录)
        System.out.println("日志:开始执行 " + method.getName() + " 方法,参数:" + (args != null ? args[0] : "无"));
        
        // 调用目标对象的核心方法(反射调用)
        Object result = method.invoke(target, args);
        
        // 增强逻辑 3:方法执行后(日志记录)
        System.out.println("日志:" + method.getName() + " 方法执行完毕\n");
        
        return result; // 返回目标方法的执行结果
    }
}
3. 测试类(生成代理对象并调用)
复制代码
import java.lang.reflect.Proxy;
​
public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 2. 创建增强逻辑处理器(注入目标对象)
        InvocationHandler handler = new MyInvocationHandler(target);
        
        // 3. 动态生成代理对象
        // 参数 1:目标对象的类加载器
        // 参数 2:目标对象实现的所有接口(JDK 代理基于接口)
        // 参数 3:增强逻辑处理器
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
        
        // 4. 通过代理对象调用方法
        proxy.addUser("王五");
        proxy.addUser("赵六");
    }
}
输出结果
复制代码
权限校验:验证用户是否有权限执行 addUser 方法
日志:开始执行 addUser 方法,参数:王五
核心逻辑:新增用户 -> 王五
日志:addUser 方法执行完毕
​
权限校验:验证用户是否有权限执行 addUser 方法
日志:开始执行 addUser 方法,参数:赵六
核心逻辑:新增用户 -> 赵六
日志:addUser 方法执行完毕
JDK 动态代理的优缺点
优点 缺点
无需手动编写代理类,灵活高效 只能代理 实现了接口 的目标类(无法代理无接口的类)
增强逻辑统一管理,无代码冗余 基于反射,有轻微运行时开销(可忽略)
JDK 自带,无需额外依赖 无法直接代理目标类的静态方法、final 方法

2. CGLIB 动态代理

核心原理
  • CGLIB(Code Generation Library)是一个字节码生成库,通过 继承目标类 生成代理类(子类代理);

  • 无需目标类实现接口,可代理任意非 final 类(final 类无法被继承);

  • 依赖 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口;

  • 增强逻辑写在 MethodInterceptor 的 intercept() 方法中。

依赖导入(Maven)

CGLIB 需手动导入依赖(Spring 框架已内置 CGLIB,若使用 Spring 可省略):

复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 推荐使用最新稳定版 -->
</dependency>
实现步骤
  1. 定义目标类(无需实现接口);

  2. 实现 MethodInterceptor 接口,编写增强逻辑;

  3. 通过 Enhancer 类生成代理对象并调用方法。

代码示例:日志增强(无接口目标类)
1. 目标类(无接口)
复制代码
// 目标对象:无接口,非 final 类(CGLIB 基于继承)
public class OrderService {
    // 核心业务方法(非 final,否则无法被代理增强)
    public void createOrder(String orderNo) {
        System.out.println("核心逻辑:创建订单 -> " + orderNo);
    }
}
2. 实现 MethodInterceptor(增强逻辑处理器)
复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
​
// 增强逻辑处理器:实现 MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * 所有代理方法的调用都会转发到 intercept() 方法
     * @param obj 代理对象(子类实例)
     * @param method 目标方法
     * @param args 目标方法参数
     * @param proxy 方法代理对象(用于调用目标方法,比反射更高效)
     * @return 目标方法返回值
     * @throws Throwable 目标方法可能抛出的异常
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 增强逻辑:方法执行前
        System.out.println("日志:开始执行 " + method.getName() + " 方法,参数:" + args[0]);
        
        // 调用目标对象的核心方法(两种方式)
        // 方式 1:通过 MethodProxy 调用(推荐,比反射高效)
        Object result = proxy.invokeSuper(obj, args);
        // 方式 2:通过反射调用(同 JDK 代理)
        // Object result = method.invoke(target, args);
        
        // 增强逻辑:方法执行后
        System.out.println("日志:" + method.getName() + " 方法执行完毕\n");
        
        return result;
    }
}
3. 测试类(生成代理对象并调用)
复制代码
import net.sf.cglib.proxy.Enhancer;
​
public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建增强逻辑处理器
        MethodInterceptor interceptor = new MyMethodInterceptor();
        
        // 2. 通过 Enhancer 生成代理对象(子类代理)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class); // 设置父类(目标类)
        enhancer.setCallback(interceptor); // 设置增强逻辑处理器
        
        // 3. 生成代理对象(强制转换为目标类类型)
        OrderService proxy = (OrderService) enhancer.create();
        
        // 4. 通过代理对象调用方法
        proxy.createOrder("ORDER_20251123");
        proxy.createOrder("ORDER_20251124");
    }
}
输出结果
复制代码
日志:开始执行 createOrder 方法,参数:ORDER_20251123
核心逻辑:创建订单 -> ORDER_20251123
日志:createOrder 方法执行完毕
​
日志:开始执行 createOrder 方法,参数:ORDER_20251124
核心逻辑:创建订单 -> ORDER_20251124
日志:createOrder 方法执行完毕
CGLIB 动态代理的优缺点
优点 缺点
无需目标类实现接口,适用范围更广 依赖第三方库,需额外导入
基于字节码生成,比 JDK 代理(反射)效率更高 无法代理 final 类 和 final 方法(子类无法重写)
可代理静态方法(需特殊配置) 生成代理类时字节码操作复杂,初始化开销略大

三、三种代理模式对比

特性 静态代理 JDK 动态代理 CGLIB 动态代理
代理方式 手动编写代理类 基于接口(反射) 基于继承(字节码生成)
目标类要求 需实现接口 需实现接口 无接口要求(非 final 类)
依赖 无 JDK 自带(java.lang.reflect) 第三方库(cglib)
灵活性 低(编译期确定) 中(运行时生成) 高(运行时生成,无接口限制)
性能 高(无反射开销) 中(反射调用) 高(字节码直接调用,比 JDK 快)
适用场景 简单场景、类数量少 目标类有接口(如 Spring AOP 默认) 目标类无接口(如 MyBatis mapper 代理)

四、代理模式的典型应用场景

  1. 日志记录:方法调用前后记录日志(如接口请求日志);

  2. 权限校验:方法执行前校验用户权限(如 Admin 接口拦截);

  3. 事务管理:方法执行前后开启 / 提交事务(如 Spring 声明式事务);

  4. 缓存控制:方法执行前查询缓存,执行后更新缓存(如 Redis 缓存);

  5. 远程调用:代理对象封装远程调用细节(如 RPC 框架中的服务代理);

  6. 延迟加载:代理对象延迟初始化目标对象(如 Hibernate 懒加载)。

经典框架应用

  • Spring AOP:默认优先使用 JDK 动态代理,目标类无接口时使用 CGLIB;

  • MyBatis:Mapper 接口代理(JDK 动态代理),无需实现类即可执行 SQL;

  • Dubbo:服务代理(支持 JDK/CGLIB),封装远程调用细节。


五、总结

代理模式的核心价值是 "解耦" :将核心业务逻辑(目标对象)与增强逻辑(日志、权限等)分离,符合 开闭原则(对扩展开放,对修改关闭)。

  • 简单场景用 静态代理(易理解、无开销);

  • 目标类有接口用 JDK 动态代理(无依赖、灵活);

  • 目标类无接口用 CGLIB 动态代理(适用范围广、效率高)。

实际开发中,很少手动编写代理类,更多是使用框架(如 Spring AOP)封装的动态代理能力,专注于业务逻辑和增强逻辑的实现即可。

相关推荐
JAVA面经实录9172 分钟前
线上故障标准处理流程【完整版|面试背诵+生产落地】
java·面试·职场和发展
殷色玫瑰13 分钟前
C++入门基础复习:从命名空间到引用与nullptr,一篇重新捡回C++基础
java·开发语言·c++
(Charon)25 分钟前
【C++面试】手写线程池:任务队列、工作线程与优雅退出
开发语言·c++
言乐631 分钟前
Python概括后端原理
开发语言·python·django·virtualenv·pygame
@大迁世界1 小时前
JEV RAG:更高效的 RAG 系统解决方案?
开发语言·rust
大侠归来1 小时前
C语言素数判断:从入门到进阶
java·c语言·算法
谢亮_vipxieliang2 小时前
Spring Boot 自动配置原理:从 @SpringBootApplication 到自定义 Starter
java·spring boot·后端
朝朝辞暮i2 小时前
C++ 第一阶段总结:从零基础到 ROS2 前置语法
开发语言·c++·算法
朝朝辞暮i2 小时前
C++ 第 25 课:getter / setter + this
java·javascript·c++
2601_962885722 小时前
如何用 Python 回测定投策略(定期定额 DCA)?(和一次性买入对比)
开发语言·人工智能·python