Java设计模式之结构型—代理模式

Java中最常用的设计模式-CSDN博客

静态代理

"在编译期就写死代理类,实现/继承同一个接口/父类,把真实对象包一层,手动加逻辑。"

  1. 场景
  • 对真实对象做 日志、权限、缓存 等横切增强

  • 真实对象不许改动(第三方、旧代码)

  • 代理类数量固定 → 适合 接口少、实现类少 的场景

代码块

复制代码
// 1) 公共接口
public interface UserService {
    void save(String name);
}

// 2) 真实对象
public class UserServiceImpl implements UserService {
    public void save(String name) {
        System.out.println("保存用户:" + name);
    }
}

// 3) 代理类(编译期手写)
public class UserServiceProxy implements UserService {
    private final UserService real;   // 组合真实对象

    public UserServiceProxy(UserService real) {
        this.real = real;
    }

    public void save(String name) {
        System.out.println("前置日志:准备保存");
        real.save(name);              // 调用真实对象
        System.out.println("后置日志:保存完成");
    }
}

// 4) 使用
UserService service = new UserServiceProxy(new UserServiceImpl());
service.save("Alice");

优缺点

|-------------|---------------------------|
| 优点 | 缺点 |
| 简单直观,无运行时开销 | 每多一个接口/实现,就要手写一个代理类 → 类爆炸 |
| 编译期即可检查类型 | 无法代理 未实现接口的方法 |

动态代理

Dynamic Proxy

|-----------------|---------------------------------------------|--------------|--------------------------|
| 方案 | 技术 | 代理目标 | 关键类 |
| JDK 动态代理 | java.lang.reflect.Proxy + InvocationHandler | 只能代理接口 | Proxy.newProxyInstance() |
| CGLIB/ByteBuddy | 字节码生成库 | 接口 + 普通类 | Enhancer.create() |

JDK 动态代理

复制代码
// 订单服务接口
public interface OrderService {
    void create();          // 创建订单的业务方法
}

// 订单服务实现类
public class OrderServiceImpl implements OrderService {
    public void create() {
        System.out.println("创建订单");  // 真正的业务逻辑
    }
}

// 日志处理器:实现 InvocationHandler 接口,用来在真实方法前后附加日志
public class LogHandler implements InvocationHandler {
    private final Object target;   // 被代理的"真实对象"

    public LogHandler(Object target) {
        this.target = target;      // 构造时把真实对象传进来
    }

    // 每当代理对象上的任何方法被调用时,都会走到这里
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置日志");                // 1. 前置增强(如记录开始时间、打印参数等)
        Object result = method.invoke(target, args);  // 2. 通过反射调用真实对象的方法
        System.out.println("后置日志");                // 3. 后置增强(如记录结束时间、打印返回值等)
        return result;                               // 4. 把真实方法的返回值原样返回
    }
}

// ---------------- 使用示例 ----------------
public class Main {
    public static void main(String[] args) {
        // 1. 用 JDK 的 Proxy 工具生成一个"代理对象"
        OrderService proxy = (OrderService) Proxy.newProxyInstance(
                OrderService.class.getClassLoader(),   // 类加载器:告诉 JVM 把代理类加载到哪个命名空间
                new Class<?>[]{OrderService.class},    // 需要实现的接口列表(可多个)
                new LogHandler(new OrderServiceImpl()) // 调用处理器:真正干活儿的 LogHandler
        );

        // 2. 通过代理对象调用方法
        proxy.create();  // 控制台会依次打印:
                         //   前置日志
                         //   创建订单
                         //   后置日志
    }
}

public Object invoke(Object proxy, Method method, Object[] args)参数含义

  1. Object proxy

    这是 代理对象本身 (就是 Proxy.newProxyInstance() 返回的那个对象)。

    • 如果你在这里面再调用 proxy.toString()proxy.create() 之类的方法,会再次进入 invoke,极易造成无限递归,所以通常不会直接用它。

    • 主要用途:在需要判断 proxy == 某个代理实例 或打印调试信息时才会用到。

  2. Method method

    本次被调用的方法对象

    • 通过 method.getName() 可以知道调的是哪个方法(如 create)。

    • 通过 method.getParameterTypes()method.getReturnType() 等可以拿到签名信息,用来做通用逻辑(例如所有 get* 方法做缓存、所有 save* 方法做事务等)。

  3. Object[] args

    本次方法调用时传进来的实参数组,按声明顺序排列。

    • 如果方法无参,它就是 null 或空数组。

    • 可以通过 args[i] 读取或修改参数值,实现诸如统一参数校验、脱敏、记录日志等横切逻辑。

CGLIB 最简示例(可代理类)

复制代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("前置");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("后置");
    return result;
});
UserService proxy = (UserService) enhancer.create();
proxy.save("Bob");

动态代理 vs 静态代理

|--------|------------------------------|------------|
| 维度 | 动态代理 | 静态代理 |
| 代码量 | 1 个 InvocationHandler 即可 | 每接口/实现都要手写 |
| 代理范围 | 任意接口/类(CGLIB) | 只能固定接口 |
| 性能 | 反射调用略慢(JDK)/ ASM 接近原生(CGLIB) | 直接调用,零反射 |
| 生成时机 | 运行时 | 编译期 |

相关推荐
东北南西4 小时前
设计模式-代理模式
设计模式·typescript·代理模式
ByteBlossom4 小时前
Java集合源码解析之LinkedList
java·开发语言
青鱼入云4 小时前
【面试场景题】1GB 大小HashMap在put时遇到扩容的过程
java·数据结构·面试
mask哥4 小时前
DP-观察者模式代码详解
java·观察者模式·微服务·设计模式·springboot·设计原则
幸幸子.4 小时前
实验2-代理模式和观察者模式设计
java·开发语言
追寻向上4 小时前
睿联科技2026年秋招内推
java·python·科技
计时开始不睡觉4 小时前
从 @Schedule 到 XXL-JOB:分布式定时任务的演进与实践
java·分布式·spring·xxl-job·定时任务
Slaughter信仰5 小时前
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第八章知识点问答(18题)
java·开发语言·jvm
We....5 小时前
Java多线程
java·开发语言