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动态代理的原理是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/u/4090547/blog/4551559