跳至正文
来两杯美式
返回

设计模式之代理模式:静态代理与 JDK 动态代理

By 来两杯美式
发布于

这是 2020 年 5 月整理的设计模式专题第七篇。代理模式是面试高频中的高频,尤其是 JDK 动态代理的底层原理——Spring AOP 就是建立在它之上。本文从概念讲到底层字节码,层层深入。

简单说明

代理模式(Proxy Pattern):为其他对象提供一种代理,以控制对这个对象的访问

客户端 ——> 代理对象 ——> 真实目标对象
             (中介、保护、增强)

静态代理

代理类在编译期就确定,一个代理类对应一个目标类(或接口):

/**
 * 描述:目标接口
 */
public interface UserService {
    void printName();
}

/**
 * 描述:真实目标对象
 */
public class UserServiceImpl implements UserService {
    @Override
    public void printName() {
        System.out.println("真实业务:打印姓名");
    }
}

/**
 * 描述:静态代理:和目标实现同一个接口,持有真实对象
 */
public class UserServiceProxy implements UserService {
    private UserService target;

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

    @Override
    public void printName() {
        System.out.println("代理增强:方法执行前记录日志");
        target.printName();
        System.out.println("代理增强:方法执行后统计耗时");
    }
}

静态代理的缺点:一个代理类只能代理一种接口,如果系统有很多接口需要增强,就要写很多代理类——代理类爆炸。动态代理应运而生。

JDK 动态代理

JDK 动态代理在运行期为接口动态生成代理类,无需手写。

/**
 * 描述:InvocationHandler:所有代理方法的调用都汇聚到这里
 */
public class UserServiceHandler implements InvocationHandler {
    private Object target;

    public UserServiceHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("动态代理增强:方法执行前");
        Object result = method.invoke(target, args);
        System.out.println("动态代理增强:方法执行后");
        return result;
    }
}

// 使用:Proxy.newProxyInstance 动态创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new UserServiceHandler(target));
proxy.printName();

前提条件被代理的类必须实现了接口(JDK 动态代理基于接口)。没有接口的实现类需要用 CGLIB(基于继承)。

导出 JDK 生成的动态代理类

# JVM 参数指令,生成 JDK 创建的动态代理类
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

或者在代码中加入:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

注意:生成的代理文件在项目根目录的 com.xxx.proxy 下(对应被代理接口的包路径)。

生成的代理类长什么样

JDK 动态代理生成的代理类(反编译后)大致如下:

/**
 * 描述:JDK 生成的 $Proxy0 代理类
 */
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws Exception {
        super(var1);
    }

    public final boolean equals(Object var1) throws Exception {
        try {
            return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void printName() throws Exception {
        try {
            super.h.invoke(this, m3, (Object[]) null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

关键结构解读

  1. 继承自 java.lang.reflect.Proxy:JDK 代理类必须继承 Proxy(这是 JDK 动态代理只能基于接口的根本原因——Java 单继承,代理类已经”占用”了继承位);
  2. 构造函数接收 InvocationHandler:传给 Proxy(InvocationHandler h),保存为 super.h
  3. 每个接口方法对应一个静态 Method 成员m1/m2/m3…);
  4. 重写的接口方法全部调用 super.h.invoke(this, mX, args):所有业务逻辑都汇聚到 InvocationHandler 的 invoke 方法——这就是 AOP 拦截的底层入口。

代理模式的应用

场景说明
Spring AOP基于 JDK 动态代理(有接口)或 CGLIB(无接口)
MyBatis Mapper接口的动态代理实现(MapperProxy)
远程代理RPC/Dubbo 中代理远程服务调用
安全代理访问控制、权限校验
日志/性能监控方法级日志、耗时统计

小结


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.软件设计原则:SOLID + KISS / YAGNI / LOD 全景
  2. 02.设计模式之原型模式,及深浅拷贝
  3. 03.设计模式之工厂模式:简单工厂、工厂方法与抽象工厂
  4. 04.设计模式之建造者模式:复杂对象的组装艺术
  5. 05.设计模式之适配器模式:让不兼容的接口协同工作
  6. 06.设计模式之桥接模式:抽象与实现分离,各自独立变化
  7. 07.设计模式之装饰者模式:动态增强,比继承更有弹性
  8. 08.设计模式之代理模式:静态代理与 JDK 动态代理
  9. 09.设计模式之外观模式:统一门面,简化子系统调用
  10. 10.设计模式之享元模式:共享细粒度对象,降低内存占用
  11. 11.设计模式之策略模式:算法家族,自由切换
  12. 12.设计模式之模板方法模式:固定骨架,可变步骤
  13. 13.设计模式之观察者模式:一对多依赖,状态变更自动通知
  14. 14.设计模式之责任链模式:请求逐级传递,动态组合处理者
  15. 15.设计模式之状态模式:状态变,行为变
  16. 16.设计模式之迭代器模式:不暴露内部实现,顺序访问集合

上一篇
DDD 复杂业务系统设计(九):中台,在线教育平台到底应该共享什么
下一篇
设计模式之装饰者模式:动态增强,比继承更有弹性