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) | 直接调用,零反射 |
| 生成时机 | 运行时 | 编译期 |

相关推荐
北风朝向13 小时前
Spring Boot参数校验8大坑与生产级避坑指南
java·spring boot·后端·spring
闭着眼睛学算法13 小时前
【华为OD机考正在更新】2025年双机位A卷真题【完全原创题解 | 详细考点分类 | 不断更新题目 | 六种主流语言Py+Java+Cpp+C+Js+Go】
java·c语言·javascript·c++·python·算法·华为od
山海不说话13 小时前
Java后端面经(八股——Redis)
java·开发语言·redis
烛阴13 小时前
【TS 设计模式完全指南】构建你的专属“通知中心”:深入观察者模式
javascript·设计模式·typescript
哈哈很哈哈13 小时前
Flink SlotSharingGroup 机制详解
java·大数据·flink
真的想不出名儿13 小时前
springboot - 邮箱验证码登录
java·springboot·邮箱验证
the beard14 小时前
JVM垃圾回收器深度解析:从Serial到G1,探索垃圾回收的艺术
java·jvm
大虾别跑14 小时前
vc无法启动
java·开发语言
郭老二14 小时前
【JAVA】从入门到放弃-01-HelloWorld
java·开发语言
卷Java14 小时前
CSS模板语法修复总结
java·前端·css·数据库·微信小程序·uni-app·springboot