JDK动态代理使用及原理解析
- 一、动态代理的使用
- 1.1 动态代理简单示例
- 1.2 创建代理实例在代理类中
- 1.3 公用的代理类
- 二、如何进入到代理类中的invoke的?
- 2.1 拿到$Proxy0这个类
- 2.2 $Proxy0的解析
- 三、Proxy.newProxyInstance是如何生成一个代理对象然后返回的?
一、动态代理的使用
1.1 动态代理简单示例
动态代理类图如下:
主体和实际主体代码如下所示:
/**
* 主体
*/
public interface Subject {
//主体方法
void subjectMethod();
}
/**
* 实际主体
*/
public class RealSubject implements Subject {
@Override
public void subjectMethod() {
System.out.println("执行方法subjectMethod...");
}
}
代理类代码如下所示,可看到代理类中使用了反射调用:
public class ProxyHandler implements InvocationHandler {
private Subject subject;
public ProxyHandler(Subject subject) {
this.subject = subject;
}
/**
* @param proxy 调用方法的代理实例
* @param method 被代理对象中的方法
* @param args 调用方法时的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法subjectMethod前");
/**
* method.invoke(Object obj, Object... args)
* @param obj :方法被调用的对象
* @param args :方法被调用的参数
* @return Object: 方法调用返回的结果
*/
Object o = method.invoke(subject, args);
System.out.println("执行方法subjectMethod后");
return o;
}
}
请求者中声明了实际主体和调动处理程序:
/**
* Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* @param loader:代理类的类加载器
* @param interfaces: 代理类要实现的接口列表
* @param h:将方法调用分派到的调用处理程序
* @return 返回代理对象,是对被代理对象的增强
*/
Subject proxySubject = (Subject) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
new Class<?>[]{Subject.class}, new ProxyHandler(new RealSubject()));
proxySubject.subjectMethod();
输出如下:
执行方法subjectMethod前
执行方法subjectMethod...
执行方法subjectMethod后
1.2 创建代理实例在代理类中
在实际应用中,创建代理实例(Proxy.newProxyInstance)往往是放在代理类中
代码如下所示:
public class ProxyHandlerOther implements InvocationHandler {
private Subject subject;
public Subject getProxySubject(Subject subject) {
this.subject = subject;
return (Subject) Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
this.subject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法subjectMethod前");
Object o = method.invoke(subject, args);
System.out.println("执行方法subjectMethod后");
return o;
}
}
请求者直接创建代理并调用方法即可:
Subject otherProxySubject = new ProxyHandlerOther().getProxySubject(new RealSubject());
otherProxySubject.subjectMethod();
1.3 公用的代理类
可将代理类中的代理对象和通知公用出来,如下所示:
代码如下:
public class ProxyHandlerCommon implements InvocationHandler {
private Object subject;
private Advice advice;
public Object getProxyObject(Object subject, Advice advice) {
this.subject = subject;
this.advice = advice;
return Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
this.subject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();
Object o = method.invoke(subject, args);
advice.after();
return o;
}
}
通知接口和默认通知类如下:
/**
* 公用通知接口
*/
public interface Advice {
void before();
void after();
}
/**
* 默认通知
*/
public class AdviceDefault implements Advice{
@Override
public void before() {
System.out.println("Advice before...");
}
@Override
public void after() {
System.out.println("Advice after...");
}
}
请求者直接指定主体和通知即可:
//公用的代理类
Object commonProxy = new ProxyHandlerCommon().getProxyObject(new RealSubject(), new AdviceDefault());
((Subject) commonProxy).subjectMethod();
如何进入到代理类中的invoke的?
请求者中使用Proxy.newProxyInstance生成了代理实例,那么代理实例是如何进入到代理类中的invoke方法的呢?
我们在请求者中设置断点如下图所示:
如上图可看到,代理实例实际上是一个名为$Proxy0的一个类,内部有个h实例(ProxyHandler),h实例内部有一个subject(RealSubject)。
猜测:这个$Proxy0也实现了Subject接口,执行主体方法subjectMethod时实际上是去执行h实例里的invoke方法。
拿到$Proxy0这个类,有以下两种方式:
2.1 拿到$Proxy0这个类
方法一:saveGeneratedFiles属性
找源码可知打开saveGeneratedFiles属性即可,在请求者中加上该属性:
//保存生成的代理类class文件,默认false
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
运行程序时,会看到项目下生成
P
r
o
x
y
0.
c
l
a
s
s
这
个
类
(
一
般
位
于
c
o
m
.
s
u
n
.
p
r
o
x
y
.
Proxy0.class这个类(一般位于com.sun.proxy.
Proxy0.class这个类(一般位于com.sun.proxy.Proxy.class)。
为什么加上该属性就可打印文件,后面会说到。
方法二、从内存中把 P r o x y 0 写 入 到 文 件 Proxy0写入到文件 Proxy0写入到文件Proxy0.class
public static void createProxyClassFile(){
/**
* JDK自带的工具,把内存里的字节码转换字节码数组
* 从内存中把$Proxy0写入到文件$Proxy0.class中
* 参数1:类名
* 参数2:该类实现的接口
*/
byte [] $Proxy0Byte = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class});
try{
//字节码数组写入到文件
FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
fileOutputStream.write($Proxy0Byte);
fileOutputStream.close();
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2.2 $Proxy0的解析
$Proxy0.class源码如下:
public final class $Proxy0 extends Proxy implements Subject {
private static Method m3;
//...省略
public final void subjectMethod() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
//...省略
m3 = Class.forName("com.mm.mmblogs.b4.Subject").getMethod("subjectMethod");
//...省略
}
}
由上可知:
- $Proxy0实现了Subject接口,所以它也有subjectMethod方法
- P r o x y 0 继 承 了 Proxy0继承了 Proxy0继承了Proxy类,里面的变量InvocationHandler即h就是Proxy.newProxyInstance时传入的ProxyHandler实例
- $Proxy0中subjectMethod方法调用了h中的invoke方法
Proxy.newProxyInstance是如何生成一个代理对象然后返回的?
- 下面代码经过手动删减
进入到Proxy.newProxyInstance方法,代码大致如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
//生成代理类对象
Class<?> cl = getProxyClass0(loader, intfs);
//拿到代理类的构造函数
final Constructor<?> cons = cl.getConstructor(constructorParams);
//返回代理实例
return cons.newInstance(new Object[]{h});
}
再来看看getProxyClass0方法:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 接口数量不可大于065535
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 缓存中存在,则直接返回
// 否则通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}
ProxyClassFactory类如下:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 生成唯一代理类名的下一个数字
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//生成指定的代理类的字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//解析字节码,生成$Proxy的Class对象并返回
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
}
}
继续跟进ProxyGenerator.generateProxyClass方法,这里还看到了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();
//判断sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//...省略
Files.write(var2, var4, new OpenOption[0]);
return null;
}
});
}
return var4;
}
generateClassFile方法中,主要是为代理类组装信息,如组装接口中的所有方法、组装构造函数、组装字段信息等等,然后写入文件,最后以字节数组的方式返回。
Proxy.newProxyInstance方法总结如下:
- 生成代理类对象,如果缓存没有则创建。创建时先生成指定的代理类的字节码文件,然后在解析称class对象返回。
- 拿到代理类的构造函数,并创建代理实例返回。
- sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件,默认为false。
- 代理类工厂ProxyClassFactory生成的代理类名前缀为$Proxy,且用AtomicLong保证各个代理类名唯一。