电器网站建设,清风夏邑进入公众号,wordpress网站导入,网站查询访问域名攻破Java技术盲点之剖析动态代理的实现原理和开发指南 背景介绍静态代理和动态代理动态代理与静态代理的区别 进入正题重温#xff1a;静态代理实现静态代理案例静态代理的弊端 重温#xff1a;动态代理Java动态代理InvocationHandlerJava动态代理的实现下面看具体的代码实例… 攻破Java技术盲点之剖析动态代理的实现原理和开发指南 背景介绍静态代理和动态代理动态代理与静态代理的区别 进入正题重温静态代理实现静态代理案例静态代理的弊端 重温动态代理Java动态代理InvocationHandlerJava动态代理的实现下面看具体的代码实例目标接口类和对应实现类定义和实现调用处理器目标方法的具体测试效果三个参数的意义如下动态代理神奇的地方代理调用和目标调用 Java动态代理的好处Java动态代理的总结归纳Java动态代理的原理剖析代理调用的实现i原理Proxy.newProxyInstancejava.lang.reflect.Proxy#getProxyClass0 java.lang.reflect.Proxy.ProxyClassFactory#applyProxyGenerator.generateProxyClass输出对应的生产proxy的class代码 Proxy代理源码流程总结Java动态代理的弊端 Cglib动态代理区别在于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{Overridepublic String test(String str) {return exec: str;}
}通过日志记录来追踪test()方法的调用你可以通过静态代理来实现这一目标。
重温静态代理
因为需要对一些函数进行二次处理或是某些函数不让外界知道时可以使用代理模式通过访问第三方间接访问原函数 的方式达到以上目的来看一下代理模式的类图
实现静态代理案例
通过创建一个实现了相同接口的代理类并在代理类中调用目标类的方法并记录日志来实现对test()调用的日志记录。这样你就可以在代理类中实现对test()方法的调用和日志记录的统一管理。 静态代理可以在编译时确定代理关系实现起来相对简单。
class StaticProxiedTest implements ProxyTest{private ProxyTest proxyTest new ProxyTestImp ();Overridepublic 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动态代理的实现
开发调用处理器以实现动态代理的具体操作步骤包括以下几个关键环节
引入必要的类在开发过程中首先需要引入目标类以及与扩展方法相关的类库。这些类库将为后续的代理处理提供必要的支持。对象赋值在创建代理处理器时通常需要通过调用目标类的构造函数来为其相关对象进行赋值。这些赋值操作对于确保代理处理的正确性和一致性至关重要。逻辑合并在实现动态代理的过程中需要在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 对象是否为空为空就抛出 NullPointerExceptionObjects.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 ProxyClassFactoryimplements BiFunctionClassLoader, 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) {MapClass?, 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 inint 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 packageproxyPkg 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 PrivilegedActionVoid() {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);}}//注意这里为了节省篇幅省去了toStringhashCode、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大有帮助。
参考资料
【JDK Proxy官方文档】【cglib文档】【asm文档】