JDK动态代理的原理是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
java对设计模式--代理模式的实现,只能针对接口进行代理。代理模式:提供一个代理对象来持有目标对象的引用,通过对代理对象的操作可以达到操作目标对象的目的。使用代理模式主要是使用者不想或者不能直接操作目标对象,需要一个代理的中间对象来维持联系。例如Mybatis中Mapper接口并没有实现类,因此使用者不能直接操作实现类,所以会产生一个代理Mapper。又例如Spring AOP中的Bean,使用者想对Bean的使用进行增强或者其他处理,于是Spring需要返回一个的代理Bean来完成目的。
一接口:
public interface ITodo { void doString(String desc); }
一实现:
public class Todo implements ITodo { @Override public void doString(String desc) { System.out.println("doString: " + desc); } }
目标,对接口的原有方法进行增强。实现方式:JDK动态代理
一代理工具类:
public class ProxyInstance implements InvocationHandler { // 代理目标,即被代理类 private Object target; // 代理类持有被代理类 public ProxyInstance(Object target) { this.target = target; } public <T> T getProxy(){ // 获取实例方式,这里使用newProxyInstance return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } // 这里为代理的处理流程 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy before"); Object obj=method.invoke(target,args); System.out.println("proxy after"); return obj; } }
public class Test { public static void main(String[] args) { // 开启保存代理中生成文件的代码 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ITodo todo = new ProxyInstance(new Todo()).getProxy(); todo.doString("------ nothing-------- "); } }
proxy before doString: ------ nothing-------- proxy after
通过控制台就可以看到接口原有的方法被增强了,方法执行之前和执行之后执行其他代码。这就是JDK的动态代理功能。JDK动态代理的使用组件:一接口、一实现、一代理工具类,并且需要遵循以下规则:
作为参数传入
代理类需要使用Proxy的构造方法获取实例
一般使用newProxyInstance
定义代理处理逻辑
用于通过反射生成代理类的方法
因此只能代理接口中已定义的方法
接口的所有方法都会被重写为final类型
用于加载新生成的代理类$Proxy{n}
参数1: 类加载器
参数2: 接口Class数组
参数3: InvocationHandler接口实现类
构造参数为被代理类实现的接口
代理类调用的对象是Proxy类产生的实例,与被代理类不是一个对象
每次调用都需要通过反射来调用
public Object invoke(Object proxy, Method method, Object[] args)
proxy为JDK生成的代理对象$Proxy{n}对象,因此是动态的,只存在于内存中
Method为JDK例如反射获取的调用方法
args为调用方法的参数
因为该类是生成的,所有需要类加载器从新加载
对上面的示例,查看其生成的代理类源码如下
package com.sun.proxy; import cn.tinyice.demo.proxy.jdk.ITodo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; // 代理对象集成java.lang.reflect.Proxy并实现了被代理对象的父接口 // 这里调整了方法位置,便于分析 public final class $Proxy0 extends Proxy implements ITodo { private static Method m1; private static Method m2; private static Method m3; private static Method m0; static { // 需要对接口的equals、toString、hashCode和 目标方法进行重写,因此先获取原方法的运行时表示java.lang.reflect.Method try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("cn.tinyice.demo.proxy.jdk.ITodo").getMethod("doString", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } // 构造器入参:InvocationHandler实例即代理类实例 public $Proxy0(InvocationHandler var1) throws { // 判断InvocationHandler不为空,然后赋给变量h super(var1); } // ----------------------------------------- 代理主要关注点: 目标方法 重写 ------------------------ public final void doString(String var1) throws { try { // super.h=InvocationHandler实例,调用其invoke方法,该方法为用户重写的方法 // 三个参数来源确定: proxy=this,method = target的method,args=参数的Object数组(发生类型转换) super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } // ----- equals、toString、hashCode 重写,实质调用 InvocationHandler的对应方法------- 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 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); } } }
源码部分主要分为四部分
新生成的代理类定义为:public final class $Proxy0 extends Proxy implements ITodo {...}
,就是重新生成一个新的接口实现类,对原有实现类进行功能复制、增强。
equals、hashCode、toString
这三个方法是Object方法,主要是验证Java对象的唯一性,与原来的实现类已经不是一个内存地址了以及其他操作。
doString
是原有实现类的方法
InvocationHandler就是使用者编写的代码,这一步就是切入
所有被重写的方法都变为了final
类型。所有的方法都调用了InvocationHandler的invoke
方法。因此这个invoke
方法就是增强的核心方法。
了解以上内容基本可以知道JDK动态代理的底层原理了。一句话:重写方法调用自定义的invoke
来实现增强
调用入口:获取代理类
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... }
核心代吗
Class<?> cl = getProxyClass0(loader, intfs);
↓
proxyClassCache.get(loader, interfaces)
proxyClassCache在Proxy类中静态定义
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache实例,一个弱引用缓存对象。存储结构如下,是 K,P,V三个对象存储
ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
该对象需要2个工厂类,一个用于生成subKey,一个用于生成value
KeyFactory、ProxyClassFactory 均为BiFunction<ClassLoader, Class<?>[], Object>,具有apply方法
获取时和常规缓存使用方式一致,先通过key从缓存Map中获取,获取不到就去生成
ConcurrentMap<Object, Supplier<V>
。首次使用必是生成。
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // 这里生成subKey用于缓存的key factory = new Factory(key, parameter, subKey, valuesMap); // 这个工厂是实现Supplier<V>Factory的get实现中调用valueFactory的apply即ProxyClassFactory // //#apply,并最终返回了value value = Objects.requireNonNull(valueFactory.apply(key, parameter));
subkey只是中间的关联变量,不需要关注,只需要关注value也就是代理类的生成。生成入口
ProxyClassFactory#apply(ClassLoader loader, Class<?>[] interfaces)
所有代理类的字节码定义都在该方法中:主要如下
String proxyName = proxyPkg + proxyClassNamePrefix + num; // com.sun.proxy.+$Proxy+num --->com.sun.proxy.$Proxy0
proxyPkg在接口的访问修饰符是public时=“com.sun.proxy“,否则=被代理类的包名
proxyClassNamePrefix=”$Proxy“
num为原子递增AtomicLong,与生成代理类的个数相关
接口访问修饰符 int accessFlags = Modifier.PUBLIC | Modifier.FINAL 即 public final
非public接口会重写为final
所有方法都是 public final
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
此时新生成的代理类已经加载到JVM中去了
// 是否生成文件属性读取 private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); 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; } 字节码生成,忽略部分代码 private byte[] generateClassFile() { // equals、toString、hashCode 重写 this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); // 接口所有方法重写 Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } // ... ignore ... }
关于JDK动态代理的原理是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。