JDK动态代理简介
什么是JDK动态代理?
先看看代理的概念——代理:为其他对象提供一种代理以控制对这个对象的访问。
代理在生活中很常见,比如买火车票可以不去火车站,而是通过代售点;想要逃课了,就让同学代签到等等。
说白了,就是可以通过代理来完成目标事件。(自我理解)
不同于静态代理直接采用编码的方式实现,JDK动态代理是利用反射机制在运行时创建代理类,进而调用相应的方法。
JDK动态代理的使用
通过一个小例子来看看如何使用JDK动态代理。
目标接口
public interface GameService {
String beginGame(String s);
void playGame(String s);
}
目标接口中定义了两个目标方法。
目标对象
public class GamePlay implements GameService {
public String beginGame(String name) {
System.out.println("玩家" + name + "进入召唤师峡谷,游戏开始!");
return "玩家名字是:" + name;
}
public void playGame(String s) {
System.out.println("玩家说: " + s);
}
}
目标对象是目标接口的实现,也是要被代理的对象。
调用处理器——InvocationHandler
public class GameInvocationHandler implements InvocationHandler {
private Object target;
//构造方法(可传入目标对象)
public GameInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//业务逻辑
System.out.println("before-----欢迎来到——动态召唤师峡谷");
//方法的返回值(可以为空)
Object object = method.invoke(target, args);
//业务逻辑
System.out.println("after------欢迎离开——动态召唤师峡谷");
return object;
}
}
InvocationHandler是调用处理器接口,它只定义了唯一一个 invoke() 方法,通过重写该方法可自定义代理的业务逻辑,该方法三个参数:
- proxy:代理类对象($Proxy0),在该方法中一般不会用到
- method:被代理类调用的方法对象
- args:方法对象中的参数
创建代理类对象——Proxy.newProxyInstance()
public class GamePlayDynamicProxy {
public static void main(String[] args) {
//目标类(实现类)
GameService gameService = new GamePlay();
//调用处理器
InvocationHandler invocationHandler = new GameInvocationHandler(gameService);
//生成代理对象
GameService GameServiceproxy = (GameService) Proxy.newProxyInstance(gameService.getClass().getClassLoader(),
gameService.getClass().getInterfaces(), invocationHandler);
//调用方法,实际上运行的是真实类的方法(打断点可知)
GameServiceproxy.playGame("我要超神啦!");
//beginGame()可获取返回值
//String resultFromInvoke = GameServiceproxy.beginGame("LBJ");
//System.out.println("InvocationHandler中的返回值:" + resultFromInvoke);
}
}
通过Proxy类的静态方法 newProxyInstance() 来创建代理类对象实例,关于其中具体的细节在下面的实现原理中将会提及。
输出:
before-----欢迎来到——动态召唤师峡谷
玩家说: 我要超神啦!
after------欢迎离开——动态召唤师峡谷
可见,通过使用动态代理,可以在目标方法中增加所需要的业务逻辑(如日志处理、事务管理等)。
JDK动态代理使用步骤
1. 定义目标方法的接口(必须)
2. 实现接口目标方法的实现类(不是必须,如RPC中客户端代理没有实现类/Mybatis中的mapper接口也没有实现类,代理过程中实现 )
3. 定义InvocationHandler,在invoke()方法中实现代理的业务逻辑,构造InvocationHandler时可传入必要的参数,以便在invoke()中使用。例如,可传入实现类作为构造参数。
4. 调用Proxy.newProxyInstance()生成代理类对象。该方法传入三个参数:
- ClassLoader:代理对象的类加载器;
- Class[] interfaces:代理对象需要实现的接口;
- InvocationHandler:自定义实现的InvocationHandler
5.通过代理类对象运行目标方法。
实现原理
那么 Java 的动态代理是如何实现的?
首先开门见山直接进入生成代理类对象的Proxy.newProxyInstance()方法。
Proxy.newProxyInstance()方法
@CallerSensitive
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);
}
//通过类加载器和指定接口,生成代理类字节码文件,并获取代理类Class对象
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取指定构造函数对象(也就是 $Proxy0(InvocationHandler h))
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...省略
}
在这个方法中主要做了以下工作(如代码中的注释部分):
- 通过类加载器和指定接口,生成代理类字节码文件,并获取代理类Class对象cl;
- 通过Class对象cl获取指定的构造函数对象cons;
- 构造函数对象cons,通过方法中传入的InvocationHandler,生成一个实例,这个实例就是代理类对象。
可见反射在上述代码中起了重要作用。
大致了解了代理类对象是如何生成的,那么被代理的方法是如何调用的呢? InvocationHandler中重写的invoke()方法又是如何调用的呢?接下来我们就来看看代理类究竟长什么模样。
反编译
参考其他文章可知最终生成代理类的字节码是这样一行代码:
//生成字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
调用了ProxyGenerator.generateProxyClass()方法, 因此我们可以通过该方法测试生成的代理类对象Class文件,并对其进行反编译,查看生成的具体代理类究竟是什么模样。
先获取代理类Class文件:
public static void main(String[] args) {
byte[] proxyBytes = ProxyGenerator.generateProxyClass("ProxyObject", new Class[]{GameService.class});
File file = new File("E:\\ProxyObject.class");
try {
FileOutputStream out = new FileOutputStream(file);
out.write(proxyBytes);
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
将获取到的Class文件存入E盘,即ProxyObject.class文件,然后再通过一个在线反编译的网站进行反编译,获取代理类的源码:
import com.proxy.GameService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class ProxyObject
extends Proxy
implements GameService
{
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public ProxyObject(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void playGame(String paramString)
{
try
{
h.invoke(this, m4, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String beginGame(String paramString)
{
try
{
return (String)h.invoke(this, m3, new Object[] { paramString });
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.proxy.GameService").getMethod("playGame", new Class[] { Class.forName("java.lang.String") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.proxy.GameService").getMethod("beginGame", new Class[] { Class.forName("java.lang.String") });
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
观察上述代理类源码可以发现,包含了GameService接口中定义的两个方法playGame()和beginGame()。并且,
当代理类对象调用目标方法时,实际上调用的就是InvocationHandler中的invoke()方法,因此也就解释代理类是如何与invoke()方法相关联的。
以playGame方法为例:
public final void playGame(String paramString) 即调用了
h.invoke(this, m4, new Object[] { paramString });
h:构建代理类传入的InvocationHandler
m4: static 代码块中定义的method实例
paramString:参数
代理类特性
观察上述代理类源码,发现动态生成的代理类有以下特点:
1. 继承了Proxy类,实现了所代理的接口(不能再继承其他类);
2. 除了包含所代理的接口方法,还有Object类的equals()、hashCode()、toString()三个方法,目的应该是防止重写保证和目标类一致;
3. 每一个方法都会执行调用处理器的 invoke() 方法(在其中可实现自定义的逻辑);
4. 提供了一个使用InvocationHandler作为参数的构造方法,通过前面源码分析也能知道;
5. 每个代理类实例都会关联一个调用处理器对象。传入不同的调用处理器对象可生成不同的代理类对象。