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() 动态生成代理对象;

  • 增强逻辑统一写在 InvocationHandlerinvoke() 方法中。

实现步骤
  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 接口;

  • 增强逻辑写在 MethodInterceptorintercept() 方法中。

依赖导入(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)封装的动态代理能力,专注于业务逻辑和增强逻辑的实现即可。

相关推荐
好学且牛逼的马22 分钟前
【Java编程思想|15-泛型】
java·windows·python
日月星辰Ace26 分钟前
JDK 工具学习系列(五):深入理解 javap、字节码与常量池
java·jvm
G***T69130 分钟前
Python项目实战
开发语言·python
悟空码字31 分钟前
Spring Boot 整合 Elasticsearch 及实战应用
java·后端·elasticsearch
sino爱学习34 分钟前
Guava 常用工具包完全指南
java·后端
雨中飘荡的记忆36 分钟前
Spring动态代理详解
java·spring
若水不如远方1 小时前
深入理解Reactor:从单线程到主从模式演进之路
java·架构
爱分享的鱼鱼1 小时前
Java高级查询、分页、排序
java
Sɪʟᴇɴᴛ໊ོ2351 小时前
Anyview数据结构第一章(按需自取)
c语言·开发语言·数据结构·算法