写点什么

精彩推荐 |【Java 技术专题】「重塑技术功底」攻破 Java 技术盲点之剖析动态代理的实现原理和开发指南(上)

作者:洛神灬殇
  • 2024-01-14
    江苏
  • 本文字数:10485 字

    阅读完需:约 34 分钟

精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)

背景介绍

在 Java 编程中,动态代理的应用非常广泛。它被广泛应用于 Spring AOP 框架、Hibernate 数据查询、测试框架的后端 mock、RPC 以及 Java 注解对象获取等领域。

静态代理和动态代理

与静态代理不同,动态代理的代理关系是在运行时确定的,这使得它在灵活性上更胜一筹。相比之下,静态代理的代理关系在编译时就确定了,实现起来相对简单,适用于代理类较少且确定的情况。然而,动态代理提供了更大的灵活性,能够更好地应对复杂的编程需求。

动态代理与静态代理的区别


本篇文章主要来重塑和探讨 Java 中两种常见的动态代理方式:JDK 原生动态代理和 CGLIB 动态代理。

进入正题

为了做一个参考,我们先试用一个静态代理模式的案例为基础,从而衬托出动态代理的优势和灵活,先来看看静态代理模式,先从直观的示例说起,假设我们有一个接口 ProxyTest 和一个简单实现 ProxyTestImp,这是 Java 中常见的模式,使用接口来定义协议,然后通过不同的实现类来具体实现这些行为。


public interface ProxyTest{    String test(String str);}// 实现public class ProxyTestImp implements ProxyTest{    @Override    public String test(String str) {        return "exec: " + str;    }}
复制代码


通过日志记录来追踪 test()方法的调用,你可以通过静态代理来实现这一目标。

重温:静态代理

因为需要对一些函数进行二次处理,或是某些函数不让外界知道时,可以使用代理模式,通过访问第三方,间接访问原函数的方式,达到以上目的,来看一下代理模式的类图:


实现静态代理案例

通过创建一个实现了相同接口的代理类,并在代理类中调用目标类的方法并记录日志,来实现对 test()调用的日志记录。这样,你就可以在代理类中实现对 test()方法的调用和日志记录的统一管理。



静态代理可以在编译时确定代理关系,实现起来相对简单。


class StaticProxiedTest implements ProxyTest{    private ProxyTest proxyTest = new ProxyTestImp ();    @Override    public String test(String str) {        logger.info("You said: " + str);        return proxyTest .test(str);    }}
复制代码

静态代理的弊端

当需要为多个类进行代理时,建立多个代理类会导致维护难度增加。


静态代理之所以存在这些问题,是因为代理关系在编译期就已经确定。然而,如果在运行期才确定代理哪个类,那么解决这些问题会更加简单。因此,动态代理的存在变得非常必要,它提供了更大的灵活性,能够更好地应对这类问题。

重温:动态代理

动态代理模式是 Java 中常见的一种设计模式,它可以动态地创建代理对象,对方法进行拦截和处理。动态代理模式有两种实现方式,一种是基于 Java 的内置支持,称为 Java 动态代理;另一种是使用第三方库,如 cglib。


Java 动态代理

Java 动态代理是通过接口来实现的,它要求被代理的对象必须实现一个或多个接口。在运行时,Java 动态代理会生成一个实现了这些接口的代理类,该代理类继承了 java.lang.reflect.Proxy 类,并使用 InvocationHandler 作为参数来设置对方法调用的处理逻辑。

InvocationHandler

Java 动态代理模式里面有个调用处理器的概念,在 JDK 中,实现了 InvocationHandler 这个接口的类就是一个调用处理器类,其中使用了些反射的相关技术。


调用处理器的概念:请求到后台服务,会先经过调用处理器,之后才会到后台服务。然后继续有之后的操作,就像一个过滤网,一层层的过滤,只要满足一定条件,才能继续向后执行。


调用处理器的作用:控制目标对象的目标方法的执行。


Java 动态代理的实现

开发调用处理器以实现动态代理的具体操作步骤包括以下几个关键环节:


  1. 引入必要的类:在开发过程中,首先需要引入目标类以及与扩展方法相关的类库。这些类库将为后续的代理处理提供必要的支持。

  2. 对象赋值:在创建代理处理器时,通常需要通过调用目标类的构造函数来为其相关对象进行赋值。这些赋值操作对于确保代理处理的正确性和一致性至关重要。

  3. 逻辑合并:在实现动态代理的过程中,需要在 invoke 方法中巧妙地结合各种逻辑处理。这个方法决定了目标方法是否被调用,以及如何响应和处理这些调用。通过合理地组织这些逻辑,可以确保代理处理器能够根据需求动态地扩展和调整其行为。

下面看具体的代码实例

目标接口类和对应实现类


先定义一个代理接口类


/**  * 目标接口:  * 包含目标方法的声明  */  public interface TargetInterface {     void exec();  } 
复制代码


先定义一个代理接口类的实现类,用于作为被代理的实际对象。


/**  * 被代理的类  * 目标对象类  * 实现目标接口.  * 继而实现目标方法。  */  public class TargetObject implements TargetInterface {      @Override      public void exec() {          System.out.println("exec");      }  }
复制代码


定义和实现调用处理器


首先,实现一个 InvocationHandler,方法调用会被转发到该类的 invoke()方法。然后在需要使用 TargetObject 的时候,通过 JDK 动态代理获取 TargetObject 的代理对象。


import java.lang.reflect.InvocationHandler;  import java.lang.reflect.Method;    /**  * 动态代理-拦截器  */  public class MyInvocationHandler implements InvocationHandler {      private Object target;//目标类        public MyInterceptor(Object target) {          this.target = target;      }        /**      * args 目标方法的参数      * method 目标方法      */      @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          System.out.println("aaaaa");//切面方法a();          method.invoke(this.target, args);//调用目标类的目标方法          System.out.println("bbbbb");//切面方法f();          return null;      }  }  
复制代码


目标方法的具体测试效果


具体通过调用代理对象,来调用目标对象的目标方法的具体测试


import java.lang.reflect.Proxy;    public class MainTest {    public static void main(String[] args) {          //目标对象          TargetObject target = new TargetObject();          //拦截器          MyInvocationHandler myInterceptor = new MyInvocationHandler (target);          /*          *  Proxy.newProxyInstance参数:          *  1、目标类的类加载器          *  2、目标类的所有的接口          *  3、拦截器          */          //代理对象,调用系统方法自动生成          TargetInterface proxyObj = (TargetInterface)           Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInterceptor);          proxyObj.exec();      }  }  
复制代码


上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,该方法会根据指定的参数动态创建代理对象。


三个参数的意义如下



newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给 InvocationHandler.invoke()方法。


动态代理神奇的地方



代理调用和目标调用


  • 代理调用:在 invoke()方法中,你可以自由地加入各种逻辑,比如修改方法参数、添加日志功能或安全检查功能等。通过这种方式,你可以灵活地控制代理对象的操作行为,实现更加复杂的逻辑功能。

  • 目标调用:之后我们通过某种方式执行真正的方法体,示例中通过反射调用了 TargetObject 对象的相应方法,还可以通过 RPC 调用远程方法。


注意:对于从 Object 中继承的方法,JDK Proxy 会把 hashCode()、equals()、toString()这三个非接口方法转发给 InvocationHandler,其余的 Object 方法则不会转发

Java 动态代理的好处

  • 省去了编写代理类的工作量】:通过动态代理可以很明显的看到它的好处,在使用静态代理时,如果不同接口的某些类想使用代理模式来实现相同的功能,将要实现多个代理类,但在动态代理中,只需要一个代理类就好了。

  • 灵活地重用于不同的应用场景】:动态代理实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景中。

Java 动态代理的总结归纳

类比静态代理,可以发现代理类不需要实现原接口了,而是实现 InvocationHandler。通过Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), this);来动态生成一个代理类,该类的类加载器与被代理类相同,实现的接口与被代理类相同,通过上述方法生成的代理类相当于静态代理中的代理类。


Java 动态代理在运行期决定代理对象是怎么样的,解决了静态代理的弊端。当动态生成的代理类调用方法时,会触发 invoke 方法,在 invoke 方法中可以对被代理类的方法进行增强。

Java 动态代理的原理剖析

JDK 的动态代理的类看不见摸不着,虽然可以看到效果,但是底层到底是怎么做的,为什么要求实现接口呢?

代理调用的实现 i 原理

上文说了,当动态生成的代理类调用方法时,会触发 invoke 方法。很显然 invoke 方法并不是显示调用的,它是一个回调机制,那么回调机制是怎么被调用的呢?


上述动态代理的代码中,唯一不清晰的地方只有 Proxy 创建代理对象,如下所示:


Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
复制代码
Proxy.newProxyInstance

我们先来分析一下对应的 JDK 的源码:


    public static Object newProxyInstance(ClassLoader loader,                                          Class<?>[] interfaces,                                          InvocationHandler h)        throws IllegalArgumentException{        // 判空,判断 h 对象是否为空,为空就抛出 NullPointerException        Objects.requireNonNull(h);        final Class<?>[] intfs = interfaces.clone();        final SecurityManager sm = System.getSecurityManager();        if (sm != null) {         // 进行包访问权限、类加载器等权限检查           checkProxyAccess(Reflection.getCallerClass(), loader, intfs);        }        /*         * Look up or generate the designated proxy class.         * 查找或生成指定的代理类         */        Class<?> cl = getProxyClass0(loader, intfs);        // 省略若干代码    }
复制代码


第一步,尝试获取代理类,该代理类可能会被缓存,如果没有缓存,那么进行生成逻辑.

java.lang.reflect.Proxy#getProxyClass0
    private static Class<?> getProxyClass0(ClassLoader loader,                                           Class<?>... interfaces) {        // 数量超过 65535 就抛出异常,665535 这个就不用说了吧        if (interfaces.length > 65535) {            throw new IllegalArgumentException("interface limit exceeded");        }        // 如果代理类已经通过类加载器对给定的接口进行实现了,那么从缓存中返回其副本        // 否则,它将通过ProxyClassFactory创建代理类        return proxyClassCache.get(loader, interfaces);    }
复制代码


最后发现会对生成的代理类进行缓存,有了,就不直接返回,没有的,还得生成代理类,我们继续往下走:


proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
复制代码

java.lang.reflect.Proxy.ProxyClassFactory#apply

关键点在于 ProxyClassFactory 这个类,从名字也可以猜出来这个类的作用。看看代码:


/**     * A factory function that generates, defines and returns the proxy class given     * the ClassLoader and array of interfaces.     */    private static final class ProxyClassFactory        implements BiFunction<ClassLoader, Class<?>[], Class<?>>    {        // prefix for all proxy class names 定义前缀        private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names 原子操作,适用于多线程 private static final AtomicLong nextUniqueNumber = new AtomicLong(); public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try {            // 通过反射获取到接口类 interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { }         // 所得到的接口类与传进来的不相等,说明不是同一个类 if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } }
String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } }
if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement();       // 生产代理类的名字 String proxyName = proxyPkg + proxyClassNamePrefix + num; // 一些验证、缓存、同步的操作,不是我们研究的重点 /* * Generate the specified proxy class. * 生成特殊的代理类 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
复制代码


ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);``,这段代码即为生成动态代理类的关键,执行完后会返回该描述该代理类的字节码数组.随后程序读取该字节码数组,将其转化为运行时的数据结构-Class 对象,作为一个常规类使用.


    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);        final byte[] var4 = var3.generateClassFile();        // 如果声明了需要持久化代理类,则进行磁盘写入.        if (saveGeneratedFiles) {            AccessController.doPrivileged(new PrivilegedAction<Void>() {                public Void run() {                    try {                        int var1 = var0.lastIndexOf(46);                        Path var2;                        if (var1 > 0) {                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));                            Files.createDirectories(var3);                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");                        } else {                            var2 = Paths.get(var0 + ".class");                        }                        Files.write(var2, var4, new OpenOption[0]);                        return null;                    } catch (IOException var4x) {                        throw new InternalError("I/O exception saving generated file: " + var4x);                    }                }            });        }        return var4;    }
复制代码


这里我们找到了一个关键的判断条件-saveGeneratedFiles,即是否需要将代理类进行持久化.

ProxyGenerator.generateProxyClass

public class ProxyGeneratorUtils {    /**     * 把代理类的字节码写到硬盘上      * @param path 保存路径      */    public static void writeProxyClassToHardDisk(String path) {// 获取代理类的字节码          byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.class.getInterfaces());         FileOutputStream out = null;         try {            out = new FileOutputStream(path);            out.write(classFile);            out.flush();        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                out.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}
复制代码


跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等操作,前面的步骤并不是我们关注的重点,而最后它调用了


byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
复制代码


该方法用来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码 byte[]数组

输出对应的生产 proxy 的 class 代码

在 main 函数中加入System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");,会在根目录下生成了一个 $Proxy0.class 文件,把 Class 文件反编译后可以看见如下代码:


import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import proxy.TargetObject;
public final class $Proxy0 extends Proxy implements TargetObject{ private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** * 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 * 为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个 * 被代理对象的实例 * * super(paramInvocationHandler),是调用父类Proxy的构造方法。 * 父类持有:protected InvocationHandler h; * Proxy构造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //这个静态块本来是在最后的,我把它拿到前面来,方便描述 static { try { //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("proxy.TargetObject").getMethod("exec", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } /** * *这里调用代理对象的exec方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。 *this.h.invoke(this, m3, null);这里简单,明了。 *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象, *再联系到InvacationHandler中的invoke方法。嗯,就是这样。 */ public final void exec() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
}
复制代码


动态代理类不仅代理了显示定义的接口中的方法,而且还代理了 java 的根类 Object 中的继承而来的 equals()、hashcode()、toString()这三个方法,并且仅此三个方法。可以在上述代码中看到,无论调用哪个方法,都会调用到 InvocationHandler 的 invoke 方法,只是参数不同。

Proxy 代理源码流程总结

Java 动态代理的弊端

代理类和委托类需要实现同一个接口,这意味着只有实现了某个接口的类才能使用 Java 动态代理机制。然而,在实际情况中,并非所有类都会实现接口。因此,对于没有实现接口的类,Java 动态代理机制无法使用。而 CGLIB 则可以实现对类的动态代理,弥补了 Java 动态代理的不足之处。



Cglib 动态代理

cglib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。


而 cglib 是基于字节码的库,可以在运行时动态地创建子类并覆盖方法。相比 Java 动态代理,cglib 的使用更加灵活,因为它不需要被代理的对象实现接口。同时,cglib 还支持对私有方法的拦截和处理。

区别在于 Java 代理

未完待续

由于篇幅过长,会引起视觉疲劳和大脑疲劳,故此,作者会将 cglib 的的原理和实现放到了下一章:精彩推荐 |【Java 技术专题】「重塑技术功底」攻破 Java 技术盲点之剖析动态代理的实现原理和开发指南(下),希望大家多多消化本章节内容,等待下一章的到来。

总结分析

本文介绍了 Java 两种常见动态代理机制的用法和原理,JDK 原生动态代理是 Java 原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理;CGLIB 通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理 final 的情况。


动态代理是 Spring AOP(Aspect Orient Programming, 面向切面编程)的实现方式,了解动态代理原理,对理解 Spring AOP 大有帮助。

参考资料

发布于: 2024-01-14阅读数: 3
用户头像

洛神灬殇

关注

🏆 InfoQ写作平台-签约作者 🏆 2020-03-25 加入

👑 后端技术架构师,前优酷资深工程师 📕 个人著作《深入浅出Java虚拟机—JVM原理与实战》 💻 10年开发经验,参与过多个大型互联网项目,定期分享技术干货和项目经验

评论

发布
暂无评论
精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)_Java_洛神灬殇_InfoQ写作社区