背景:
笔者在学校进行实验室考核时,当时有一个issue是需要我们利用动态代理实现AOP面向切面编程记录日志。当时造这个小*的时候,前前后后花了不少时间,遂总结此文。
本小节你将收获:
了解代理模式、学会如何实现动态代理、深入探究动态代理的实现原理
@Author:Akai-yuan
我们先来举几个生活中的例子,帮助你快速理解代理的思想:你的游戏打得很菜,请代练帮你上分;某男明星开演唱会黄牛卖票;你在公司删库跑路请律师帮你打官司。这些都是代理的思想。这些中间商肯定会做一些额外的动作,比如赚点差价。
代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。
提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
2.动态代理实现过程
2.1 newProxyInstance方法 - 创建代理对象
即使是动态代理也是代理模式,那么肯定要有一个方法来创建代理对象,下面这个方法就是用于创建代理对象,即Proxy类下的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
参数介绍:
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载,获取被代理对象的ClassLoader即可(使用class类下的getClassLoader方法)。
interfaces:一个Interface对象的数组,被代理对象所实现的所有接口。
h: 一个InvocationHandler对象,就是实现InvocationHandler接口的类,表示的是当动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
2.2 invoke方法 - 调用真实对象的方法
jdk动态代理有关的类主要是Proxy类和InvocationHandler接口,两者都位于java.lang.reflect包,可见它们都是和反射有关的。关于InvocationHandler接口,他只有一个方法:invoke方法(注意不是Mehod类的invoke方法):
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数介绍:
proxy:指代我们所代理的那个真实对象的对象,也就是代理对象
method: 指代的是我们所要调用真实对象的某个方法
args:method方法的参数,以数组形式表示
2.3 编写实现InvocationHandler接口的类
要动态的创建代理对象的话,我们首先需要编写一个实现InvocationHandler接口的类,然后重写其中的invoke方法,其中target是需要被代理的对象(真实对象)。
public class MyProxy implements InvocationHandler {
/**
* 被代理的对象,即真实对象,只需要通过某种方式从本类外部获取即可
*/
private Object target;
public MyProxy() {
}
/**
*
* @param target 被代理的对象
* @return 返回代理对象
*
* 我们可以通过Proxy类通过的静态方法newProxyInstance来创建被代理对象(target)的代理对象
* target.getClass().getClassLoader() 获取被代理对象(target)的类加载器
* target.getClass().getInterfaces() 获取被代理对象(target)实现的所有接口
* this 实现InvocationHandler接口的自定义类,即本类
*/
public Object getProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 代理对象(代理类的实例)
* @param method 被代理对象需要执行执行的方法
* @param args 方法的参数
* @return 返回被代理对象(target)执行method方法的结果
* @throws Throwable 抛出异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置附加操作
System.out.println("执行目标方法之前,可以进行附加操作...");
//通过反射调用真实对象的方法
Object result = method.invoke(target, args);
//后置附加操作
System.out.println("执行目标方法之后,还可以进行附加操作...");
return result;
}
}
测试用例:
public interface UserMapper {
void add();
}
public class UserMapperImpl implements UserMapper{
@Override
public void add() {
System.out.println("在UserMapperImpl中,执行了UserMapper的add方法...");
}
}
public static void main(String[] args) {
MyProxy myProxy = new MyProxy();
//userMapper对象为真实对象
UserMapperImpl userMapper = new UserMapperImpl();
//创建代理对象
UserMapper proxy = (UserMapper) myProxy.getProxy(userMapper);
//打印真实对象的Class
System.out.println(userMapper.getClass());
//打印代理对象的Class
System.out.println(proxy.getClass());
proxy.add();
}
下图可以解释上述代码的调用过程:
那么问题来了:proxy被强制转化为了UserMapper后,add方法是否是其实现类UserMapperImpl中的add方法?
以下是控制台输出结果:
从结果中我们可以看到,创建出来的代理对象proxy的类型是"com.sun.proxy.$Proxy0",$Proxy0代理类在系统内部的编号,它并不是程序编译之后存在虚拟机中的类,而是运行时运行时动态生成类字节码,并加载到JVM中,编译完成后没有实际对应的class文件。
正是因为代理对象是运行时临时生成的,这就区别于静态代理的代理对象类需要先进行编译之后才能创建代理对象,这一点是动态代理和静态代理最大的区别,利用这点,动态代理模式创建代理对象的方式比静态代理灵活许多。
动态创建代理对象的话需要通过反射代理方法,比较消耗系统性能,但动态代理模式明显是利大于弊的。
3.代理接口
jdk动态代理代理的是接口,其实只要是一个接口,即使它没有实现类,动态代理还是可以创建出它的代理类的:
public <T> T getProxyInstance(Class<?> proxyInterface) {
Class<?>[] interfaces = null;
Class<?> clazz = null;
//如果是接口
if (proxyInterface.isInterface()) {
clazz = proxyInterface;
interfaces = new Class[]{proxyInterface};
} else {
//如果不是接口则创建一个实例对象
try {
target = proxyInterface.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
logger.severe("MyProxy类利用反射创建实例对象失败!");
}
clazz = target.getClass();
interfaces = target.getClass().getInterfaces();
}
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, this);
}
我们只需要用于一个class数组接收接口的class即可,这种直接通过接口创建代理对象的应用其中之一就是Mybatis框架,其实我们在service层注入的xxxMapper并不是一个实现类(xxxMapper并没有实现类),注入的其实是一个xxxMapper的代理对象,如:
@SpringBootTest
class ApplicationTests {
@Resource
UserMapper userMapper;
@Test
void contextLoads() {
System.out.println(userMapper.getClass());
}
}
运行结果:
那么问题又来了:
jdk动态代理可以只代理接口,没有实现类也可以,那上面提到的proxy.add()方法执行的就不是UserMapperImpl中的add方法,难道是直接执行UserMapper的add()方法?但是UserMapper是一个接口,它的add方法并没有执行体,那么proxy.add()方法到底是哪里的方法呢?
原理探究
1.代理对象执行方法的原理
public class Test {
public static void main(String[] args) {
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
MyProxy myProxy = new MyProxy();
UserMapperImpl userMapper = new UserMapperImpl();
UserMapper proxy = (UserMapper) myProxy.getProxy(userMapper);
System.out.println(userMapper.getClass());
System.out.println(proxy.getClass());
proxy.add();
}
}
我们可以使用"System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");"保存运行时生成的$Proxy0类:
该类的全部代码:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import wjh.test.UserMapper;
public final class $Proxy0 extends Proxy implements UserMapper {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
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 void add() throws {
try {
super.h.invoke(this, m3, (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);
}
}
static {
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("wjh.test.UserMapper").getMethod("add");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通过观察发现该类继承了Proxy类和实现了UserMapper接口,并且有一个方法m1,m2,m3,m0
继续观察发现这四个方法其实是通过反射获取的,其中equals、toString、hashCode方法都是Object类的方法,而其中的m3则是获取的UserMapper接口的add方法。
然后当执行$Proxy0的add方法时,执行了代码的"super.h.invoke(this, m3, (Object[])null);",这句话我们现在可能看不懂,但从字面上我们可以通过“invoke”和带括号的三个参数类型来推测这个“invoke”一定是某个类的某个方法,只是我们不清楚这个类是哪个类,这个方法是哪个方法。
那么接下来我们继续思考:
$Proxy0的add方法执行的是"super.h.invoke(this, m3, (Object[])null);",super可以理解是它的父类Proxy类,那super.h的h又是什么呢?h.invoke又是什么呢?我们看看这三个参数"this、m3、(Object[])null",它们对于的类型依次是Object、Method、Object[],这三个参数是否感觉到有那么一点点的眼熟,是否感觉好像在哪里见过?
下面就让我们看看这个感觉见过,但又想不起来的东西:
可以看到其中的invoke方法的三个参数类型依次也是:Object、Method、Object[],和上面的“super.h.invoke(this, m3, (Object[])null);”参数类型一样,而且方法名也都是invoke,这时巧合还是必然?如果是必然的,那么我们就可以推测出:
调用$Proxy0的add方法会进入MyProxy的invoke方法后先执行前置附加操作;然后执行“method.invoke(target, args)”,其中的targe就是被代理的对象UserMapperImpl,method就是则是前面通过反射获取的m3,即UserMapper接口的add方法(由于UserMapperImpl实现于UserMapper,所以执行的其实是target实现的add方法);最后再执行后置附加操作并返回结果。
那么这个推测是否正确,暂时不清楚,我们需要接着往下看。
思考:
我们上面通过“System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");”这句代码已经了解到了$Proxy0类是什么、有什么,但是我们是不是忽略了这个类到底是怎么来的呢?
创建代理对象的原理
大致过程:
直观图:
我们知道$Proxy0对象(即代理对象)是通过Proxy的newProxyInstance方法创建的,既然要知道这个对象怎么来的,那么就必须要阅读这个方法的源码了,在阅读源码之前,我们先回顾一下我们怎么调用了这个方法:
/**
*
* @param target 被代理的对象
* @return 返回代理对象
*
* 我们可以通过Proxy类通过的静态方法newProxyInstance来创建被代理对象(target)的代理对象
* target.getClass().getClassLoader() 获取被代理对象(target)的类加载器
* target.getClass().getInterfaces() 获取被代理对象(target)实现的所有接口
* this 实现InvocationHandler接口的自定义类,即本类
*/
public Object getProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
接着我们看一下源码,下面就是Proxy类的newProxyInstance方法方法的源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
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);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
对传入的InvocationHandler进行判空
首先需要对传进来的InvocationHandler类(即我们这里的MyProxy类)进行判空,因为后续使用代理对象$Proxy0调用的方法都是MyProxy类类的invoke方法,所以这个类是不可以传空值进来的。
检查真实对象是否有生成代理类的权限
其中checkProxyAccess的作用是检查是否有生成代理类的权限。
生成或查找$Proxy0类
然后是下图的方法,它的作用是查找或生成代理类$Proxy0,需要的参数是真正对象的类加载器和实现的接口,可见这个方法是重中之重,我们再后续介绍
前面提到的一个“getProxyClass0(loader, intfs)”还没有结束,只是知道了它的作用是创建一个临时代理类(即$Proxy0)或者查找已经存在虚拟机中的代理类。下面是该方法的源码:
结果我们发现它又调用了“proxyClassCache.get(loader, interfaces)”方法,经查阅资料:了解到“proxyClassCache”是缓存,其目的是为了复用,同时防止多线程重复创建同一个代理类。大家可以点进这个get方法的源码查看这个缓存机制。
如果缓存中没有代理类,那么就会生成一个新的代理类,新的代理类是在上面的ProxyClassFactory中生成的,这个类里面有一个apply方法,它返回的就是代理类$Proxy0,但是这个方法其实也只是做了一些表面工作:为代理类起名、对传入的接口数组infs做一些校验,对一些需要生成代理类的参数进行判空...... 而真正生成代理类的方法是这个方法里面调用的“ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags)”方法,它的返回值是二进制数组,在介绍这个方法之前,我们有必要简单了解字节码文件的结构:
字节码文件其实是一个Java官方严格规定字节数、格式的二进制文件,每一个字节都具有特殊的含义,比如前面4个字节:CA FE BA BE表示魔值,魔值代表它是否为一个Class文件、00 00代表Java的此版本号、00 34代表主版本号,这里就不一一介绍了,暂时只需要指定它的每个字节都有着特殊的含义即可。字节码文件存储的一个类的所有信息:版本、属性、方法......
而我们下面介绍的_generateProxyClass_方法就是按照字节码文件的规范一点一点的使用二进制来组成一个二进制数组并返回。
private class ProxyMethod {
public String methodName;
public Class<?>[] parameterTypes;
public Class<?> returnType;
public Class<?>[] exceptionTypes;
public Class<?> fromClass;
public String methodFieldName;
}
private Map<String, List<ProxyGenerator.ProxyMethod>> proxyMethods = new HashMap();
private byte[] generateClassFile() {
/*----------------第一步:将所有的方法组装成ProxyMethod对象---------------*/
//首先为代理类生成toString, hashCode, equals等代理方法
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;
//遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象
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);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
//对于具有相同签名的代理方法, 检验方法的返回值是否兼容
List var12;
while(var11.hasNext()) {
var12 = (List)var11.next();
checkReturnTypes(var12);
}
/*-------------第二步:组装要生成的class文件的所有的字段信息和方法信息-------*/
Iterator var15;
try {
//为代理类添加构造器方法
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
//遍历缓存中的代理方法
while(var11.hasNext()) {
var12 = (List)var11.next();
var15 = var12.iterator();
while(var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
//添加代理类的静态字段, 例如:private static Method m1;
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
//添加代理类的代理方法
this.methods.add(var16.generateMethod());
}
}
//添加代理类的静态字段初始化方法
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
/* -----------------------第三步:写入最终的class文件---------------------*/
//验证方法和字段集合不能大于65535
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
//验证常量池中存在代理类的全限定名
this.cp.getClass(dotToSlash(this.className));
//验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
//验证常量池存在代理类接口的全限定名
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
//接下来要开始写入文件了,设置常量池只读
this.cp.setReadOnly();
//创建字节数组输出流
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
//1.写入魔数
var14.writeInt(-889275714);
//2.写入次版本号
var14.writeShort(0);
//3.写入主版本号
var14.writeShort(49);
//4.写入常量池
this.cp.write(var14);
//5.写入访问修饰符
var14.writeShort(this.accessFlags);
//6.写入类索引
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
//7.写入父类索引, 生成的代理类都继承自Proxy
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
//8.写入接口计数值
var14.writeShort(this.interfaces.length);
Class[] var17 = this.interfaces;
int var18 = var17.length;
//9.写入接口集合
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
//10.写入字段计数值
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
//11.写入字段集合
while(var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
var20.write(var14);
}
//12.写入方法计数值
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
//13.写入方法集合
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
//14.写入属性计数值, 代理类class文件没有属性所以为0
var14.writeShort(0);
//转换成二进制数组并返回
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
在得到二进制数组以后,apply方法返回:“return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);” deffine方法的作用就是将前面得到的字节码,将其处理成真正的Java类,这样一个动态生成代理类$Proxy0就被创建出来了。
根据$Proxy0类获取它的构造器
第729行的“cl.getConstructor(constructorParams)”则是获取代理类$Proxy0的构造器,参数是“constructorParams”:
其中参数constructorParms是一个Class[]数组,只有一个元素:InvocationHandler.class
再回到我们上面看到的$Proxy0的内部,它的构造器只有一个:
可以看到$Proxy0的构造器调用了父类Proxy类的构造器,这个构造方法中把h即MyProxy类赋值给了 Proxy类的成员变量h,至此我们证明了$Proxy0类中的“super.h”就是Proxy类的成员变量h,也就是我们的MyProxy类了,那么“super.h.invoke(this, m3, (Object[])null);”就可以完全证实就是MyProxy类的invoke方法了,那么前面的假设就是正确的了。
至此,"proxy.add()"的谜团已经完全解开了:
main方法中调用“proxy.add()”实际调用的是实现UserMapper、继承Proxy类的$Proxy0类中的同名add方法,而这个同名的add方法又是调用的MyProxy类的invoke方法,而invoke方法中调用的又是通过反射调用的真实对象UserMapperImpl(即MyProxy类的target属性)的add方法,而再调用前后都可以做一些附加操作,之后把返回值返回给$Proxy0的add方法,$Proxy0的add方法又把这个返回值返回main方法,这就完成了整个动态代理过程中方法的调用。如下图所示:
反射创建$Proxy0类的对象
其中newInstance方法的参数可以再次清楚的看到用的是我们传过来的实现InvocationHandler类的MyProxy类,到这里$Proxy0类的实例对象就被创建出来了。
4.小结
综上,动态代理主要可以归纳为3个步骤:
- 运行时动态生成代理类
- 通过类加载器代理类加载到jvm
- 反射查创建对象返回
这就是为什么newProxyInstance方法需要三个参数的原因:
第一个参数是类加载器,用于代理类的字节码文件到jvm
第二个参数是真实对象实现的全部接口,因为动态生成代理类$Proxy0也需要实现这些接口
第三个参数是InvocationHandler 对象.,在代理类$Proxy0和实现的接口的同名方法中需要调用这个类中的invoke方法,然后这个方法反射调用Method类的invoke方法