前言:
本小节你将收获:了解代理模式、学会如何实现动态代理、深入探究动态代理的实现原理
@Author:Akai-yuan
@更新时间:2023/2/7
代理模式介绍
代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
简而言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。比如租房子,我们不能直接联系的房东,但是我们可以上网找到中介,中介帮你联系到房东,但是中介会进行一些附加操作,比如中间商赚差价。
动态代理实现过程
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对象上。
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方法的参数,以数组形式表示
编写实现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被强制转化为了UserMaaper,那么这个add方法是其实现类UserMapperImpl中的add方法吗?
通过结果可以看到创建出来的代理对象proxy的类型是“com.sun.proxy.$Proxy0”,$Proxy0代理类在系统内部的编号,这其实它并不是程序编译之后存在虚拟机中的类,这个类在我们写的类里面是不可能找到的。它是运行时动态生成的,即编译完成后没有实际对应的class文件,而是在运行时动态生成类字节码,并加载到JVM中。正是因为代理对象是运行时临时生成的,这就区别于静态代理的代理对象类需要先进行编译之后才能创建代理对象,这一点是动态代理和静态代理最大的区别,利用这点,动态代理模式创建代理对象的方式比静态代理灵活许多。
当然,动态创建代理对象的话需要通过反射代理方法,比较消耗系统性能,但动态代理模式明显是利大于弊的。
代理接口
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()方法到底是哪里的方法呢?
原理探究
代理对象执行方法的原理
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)”方法,它的返回值是二进制数组,在介绍这个方法之前,我们有必要简单了解字节码文件的结构: