【小家Java】JDK动态代理技术,你真学会了吗?(Proxy、ProxyClassFactory)

简介: 【小家Java】JDK动态代理技术,你真学会了吗?(Proxy、ProxyClassFactory)

前言


java界有个熟语:反射是你通向高级的敲门砖,而动态代理是你站稳高级的基础。


动态代理技术,相信我们都并不陌生。特别是在Spring框架内,大量的使用到了反射以及动态代理技术。但是如果我们只是停留在平时的运用阶段,此篇文章你其实是可以跳过的,因为反射、代理技术一般都只有在框架设计中才会使用到,业务开发是不用接触的。


一般而言,动态代理分为两种,一种是JDK反射机制提供的代理,另一种是CGLIB代理。本文主要介绍JDK动态代理的基本原理,让大家更深刻的理解JDK Proxy,知其然知其所以然。(CGLIB代理,这里也会给个Demo知道就行)


JDK动态代理真正的原理及其生成的过程


准备工作


先准备一个接口,一个实现类:

public interface Helloworld {
    void sayHello();
}
public class HelloworldImpl implements Helloworld {
    @Override
    public void sayHello() {
        System.out.print("hello world");
    }
}


JDK动态代理Demo


准备一个必须的InvocationHandler


public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method :" + method.getName() + " is invoked!");
        return method.invoke(target, args);
    }
}


然后写个Main方法实践一下:


方式一:


    public static void main(String[] args) throws Exception {
      // 传入三大参数,就能够创建出一个代理对象
    Helloworld helloWorld = (Helloworld) Proxy.newProxyInstance(
          ProxyTest.class.getClassLoader(),
          new Class<?>[]{Helloworld.class},
          new MyInvocationHandler(new HelloworldImpl())); //此处目标实现为HelloworldImpl
        helloWorld.sayHello();
    }
输出:
method :sayHello is invoked!
hello world


此种方式也是最简便的,也是最被我们熟知的创建代理的方式(所谓三大参数)


方式二:


接下来介绍另外一种方式,平时我们使用较少,了解一下即可


    public static void main(String[] args) throws Exception {
        // 三个步骤:
        // 1、生成代理接口的Class() class com.sun.proxy.$Proxy0
        // 2、拿到构造器:public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
        // 3、new一个InvocationHandler实例~~~
        Class<?> proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Helloworld.class);
        Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
        InvocationHandler ih = new MyInvocationHandler(new HelloworldImpl());
        // 通过构造函数 new出一个实例
        Helloworld helloWorld = (Helloworld) cons.newInstance(ih);
        helloWorld.sayHello();
    }
输出:
method :sayHello is invoked!
hello world


方式一的基本原理是方式二


CGLIB代理Demo


关于CGLIB的动态代理不是本文的重点,因此此处只给一个Demo:

先写一个增强器(CGLIB内部使用的增强器哦~~~~):


// 注意:这个是org.springframework.cglib.proxy.MethodInterceptor
// 而不是org.aopalliance.intercept包下的
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object intercept = methodProxy.invokeSuper(object, args); // 注意这里调用的是methodProxy.invokeSuper
        System.out.println("中介:该房源已发布!");
        return intercept;
    }
}


main方法运行:


  // 此处需要说明:Enhancer实际属于CGLIB包的,也就是`net.sf.cglib.proxy.Enhancer`
  // 但是Spring把这些类都拷贝到自己这来了,因此我用的Spring的Enhancer,包名为;`org.springframework.cglib.proxy.Enhancer`
    public static void main(String[] args) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloServiceImpl.class); // 注意此处的类型必须是实体类
        enhancer.setCallback(new MyMethodInterceptor());
        HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
        helloService.hello();
    }
输出;
this is my method~~
中介:该房源已发布!


JDK代理生成过程(原理)


我们之所以天天叫JDK动态代理,是因为这个代理class是由JDK在运行时动态帮我们生成。

为了更好的讲述,请在JVM的启动参数上加上如下启动参数:


-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true


这个参数的作用:帮我们把JDK动态生成的proxy class 的字节码保存到硬盘中,然后我们方便查看了


我的用的idea,运行main方法后会生成这个文件如下图:

image.png


可议看到生成的代理类的所在包为:com.sun.proxy,它是默认包名


贴出它生成出来的源码如下:

// 1、所有JDK动态代理  都是Proxy的子类  且自己是final类
// 2、实现了你所需要代理得接口
// 3、代理类整体看起来都是非常简单的  我们发现不管调用哪个方法,最终都是交给了InvocationHandler.invoke()方法  这也就是为什么需要我们提供这个接口的实现类的原因吧
public final class $Proxy0 extends Proxy implements Helloworld {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
  // 通过反射给Method赋值   这里我们得出结论
  // Object的三个方法equals/toString/hashCode最终都是会被代理的
  // m3是我们HelloService自己的业务方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.proxy.Helloworld").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
  // 构造函数  
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    public final boolean equals(Object var1) throws  {
        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 sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}


从代理出来的类我们可以总结如下(逻辑很简单):


  1. 静态字段:被代理的接口所有方法都有一个对应的静态方法变量;
  2. 静态块:主要是通过反射初始化静态方法变量;
  3. 具体每个代理方法:逻辑都差不多就是 h.invoke,主要是调用我们定义好的invocatinoHandler逻辑,触发目标对象target上对应的方法;
  4. 构造函数:从这里传入我们InvocationHandler逻辑;


Proxy类


一切源于Proxy这个类。我们可以看看

public class Proxy implements java.io.Serializable {
  ,,,
    /**
     * a cache of proxy classes
     */
     //KeyFactory和ProxyClassFactory都是内部类
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
  // 这是我们自己实现的执行器~~~
    protected InvocationHandler h;
  // 显然,我们并不能直接new它的对象
  // 它里面所有的方法都是静态方法
    private Proxy() {
    }
    // 这个构造函数由子类调用
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
  // 判断是否为Proxy的子类(也就是否为JDK动态代理生成的类)
  // 可以对比一下ClassUtils.isCglibProxyClass(object.getClass())  判断是否是CGLIB对象(其实就是看名字里是否含有 "$$"这个)
    public static boolean isProxyClass(Class<?> cl) {
        return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
    }
  // 这个不用说了,最重要的一个方法:生成代理对象
  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    ...
  }
  ...下面的方法再单独分析
}


我们从上面的方式二分析:getProxyClass方法是入口:需要传入类加载器和interface


    @CallerSensitive
    public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException {
    // 克隆一份
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
    // 真正做事的是这里
        return getProxyClass0(loader, intfs);
    }


然后调用getProxyClass0方法,方法的JavaDoc其实说得比较清楚了:如果实现当前接口的代理类存在,直接从缓存中返回,如果不存在,则通过ProxyClassFactory来创建


    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
      // 这里注意:我们发现允许实现接口的上线是65535个(哇,这也太多了吧  哈哈)
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
    // 这个JavaDoc说得很清楚,先从缓存拿,否则用`ProxyClassFactory`创建
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }


ProxyClassFactory


ProxyClassFactory是Proxy的一个静态内部类。它的逻辑包括了下面三步:


  1. 包名的创建逻辑

包名生成逻辑默认是com.sun.proxy,如果被代理类是non-public proxy interface(也就说实现的接口若不是public的,报名处理方式不太一样),则用和被代理类接口一样的包名,类名默认是$Proxy 加上一个自增的整数值


2.调用ProxyGenerator. generateProxyClass生成代理类字节码

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 这个参数就是在该方法起到作用,如果为true则保存字节码到磁盘。代理类中,所有的代理方法逻辑都一样都是调用invocationHander的invoke方法(上面源码我们也能看出来)


3.把代理类字节码加载到JVM。

把字节码通过传入的类加载器加载到JVM中:defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);


总结


通过这一分析,发现JDK的动态代理也不过如此嘛。


由下面几点是需要注意的:


  • toString() hashCode() equal()方法 调用逻辑:这个三个Object上的方法,如果被调用将和其他接口方法方法处理逻辑一样,都会经过invocationHandler逻辑,从上面的字节码结果就可以明显看出。其他Object上的方法将不会走代理处理逻辑,直接走Proxy继承的Object上方法逻辑。
  • interface 含有equals,toString hashCode方法时,和处理普通接口方法一样,都会走invocation handler逻辑,以目标对象重写的逻辑为准去触发方法逻辑
  • interface含有重复的方法签名,以接口传入顺序为准,谁在前面就用谁的方法,代理类中只会保留一个,不会有重复的方法签名;

目录
打赏
0
0
0
0
37
分享
相关文章
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
181 70
Java程序员在AI时代必会的技术:Spring AI
在AI时代,Java程序员需掌握Spring AI技术以提升竞争力。Spring AI是Spring框架在AI领域的延伸,支持自然语言处理、机器学习集成与自动化决策等场景。它简化开发流程,无缝集成Spring生态,并提供对多种AI服务(如OpenAI、阿里云通义千问)的支持。本文介绍Spring AI核心概念、应用场景及开发步骤,含代码示例,助你快速入门并构建智能化应用,把握AI时代的机遇。
java动态代理
本文介绍了Java中的动态代理及其优势,通过增强原有方法或拦截调用实现无侵入式代码扩展,如添加日志、缓存等。文章先讲解了静态代理的基本概念和实现方式,随后引出动态代理解决静态代理在多方法、多类场景下的局限性。通过JDK提供的InvocationHandler接口和Proxy类,展示了如何动态生成代理对象。最后,文章还探讨了代理Hook技术,包括寻找Hook点、选择代理方式以及替换原始对象的具体步骤。
Java的动态代理
Java动态代理是一种强大的机制,允许在运行时创建接口的代理实例,并拦截方法调用。其核心组件包括`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`。通过自定义接口、实现接口、编写`InvocationHandler`处理器并生成代理对象,可以灵活地增强方法功能,如日志记录、事务管理等。典型应用场景包括AOP、RPC、延迟加载和Mock测试。与CGLIB相比,JDK动态代理基于接口,性能稍慢但无需第三方库,适用于需要无侵入式增强的场合。
智慧产科一体化管理平台源码,基于Java,Vue,ElementUI技术开发,二开快捷
智慧产科一体化管理平台覆盖从备孕到产后42天的全流程管理,构建科室协同、医患沟通及智能设备互联平台。通过移动端扫码建卡、自助报道、智能采集数据等手段优化就诊流程,提升孕妇就诊体验,并实现高危孕产妇五色管理和孕妇学校三位一体化管理,全面提升妇幼健康宣教质量。
72 12
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
75 0
探索Java动态代理的奥秘:JDK vs CGLIB
深入理解 Java JDK —— 让我们从基础到进阶
JDK(Java Development Kit)是 Java 开发的核心工具包,包含编译、运行和调试 Java 程序所需的所有工具和库。它主要由 JVM(Java 虚拟机)、JRE(Java 运行时环境)和 Java 核心类库组成。JVM 是跨平台运行的基础,负责字节码的加载、执行和内存管理;JRE 提供运行 Java 应用的环境;核心类库则提供了丰富的 API 支持。通过编写、编译和运行一个简单的 Java 程序,可以深入理解 JDK 的工作原理。此外,JDK 还提供了 JIT 编译、垃圾回收优化和并发工具包等高级功能,帮助开发者提高程序性能和稳定性。
261 10
|
7月前
|
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
662 61