一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天。点击查看活动详情
一、前言
jdk动态代理一共有三种,静态代理、jdk动态代理、cglib动态代理。在spring aop中就使用了jdk动态代理和cglib动态代理。jdk动态代理作用于接口,通过实现接口并持有被代理对象的引用完成代理,是jdk原生功能。cglib是通过继承实现的,不是jdk的原生功能,需要引入第三方的包。今天主要介绍jdk动态代理。
二、使用
我们在日常的开发工作中避免不了打印日志,如果有一个需求是我们需要统计调用一个方法的耗时,可是如果我们把统计耗时的代码写在我们的业务逻辑中,难免使自己的代码显得很啰嗦,而且在代码中写了与业务逻辑无关的代码使人觉得差强人意。这时,代理的作用就体现出来了。
假设我们有一个接口UserService如下, 我门需要统计这个接口里的方法的耗时情况。
public interface UserService {
/**
* 说话
*
* @param info
*/
void say(String info);
/**
* 吃饭
*
* @param food
*/
void eat(String food);
}
实现类
public class UserServiceImpl implements UserService{
@Override
public void say(String info) {
System.out.println("There's something I want to say to you " + info);
}
@Override
public void eat(String food) {
System.out.println("I'm eating " + food);
}
}
如何使用jdk动态代理完成呢? 首先我们需要实现一个InvocationHandler。将我门需要代理的逻辑写进来
public class BasketballPlayerInvocationHandler implements InvocationHandler {
private UserService userService;
public BasketballPlayerInvocationHandler(UserService userService) {
this.userService = userService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
Object invoke = method.invoke(userService, args);
System.out.println("调用方法耗时:" + (System.nanoTime() - start));
return invoke;
}
}
然后在调用的时候使用Proxy.newProxyInstance创建代理对象。使用代理对象调用方法
public class ProxyMain {
public static void main(String[] args) {
UserService basketballPlayer = new BasketballPlayers();
basketballPlayer.getClass();
UserService proxyUserService = (UserService)Proxy.newProxyInstance(basketballPlayer.getClass().getClassLoader(), basketballPlayer.getClass().getInterfaces(),
new BasketballPlayerInvocationHandler(basketballPlayer));
proxyUserService.work("playing basketball");
proxyUserService.eat("pizza");
proxyUserService.say("法克,吹扬");
}
}
可以先看下效果
方法被调用,其中的耗时也被打印出来了。
三、Proxy
Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类。这个类来自于java.lang.reflect这个包下,从名字中就可以看出来jdk的动态代理是跟反射相关的。
代理类具有以下属性:
- 代理类的名称以 “$Proxy” 开头,后面跟着一个数字序号。
- 代理类继承了
Proxy类。 - 代理类实现了创建时指定的接口(JDK动态代理是面向接口的)。
- 每个代理类都有一个公共构造函数,它接受一个参数,即接口
InvocationHandler的实现,用于设置代理实例的调用处理器。
Proxy提供了两个静态方法,用于获取代理对象。
getProxyClass\
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
获取代理类的class对象,获取到对象可以再根据反射调用实例化出一个代理类。
newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
用于创建一个代理实例,上面代码中用的就是这个。该方法需要三个参数\
- loader: 类加载器,用于加载生成的代理类。一般使用被代理类的类加载器,因为被代理类是我们自己的代码,默认使用的是AppClassloader。代理类也就用AppClassloader去加载。如果我们在这个参数传入了其他类加载器,例如Proxy.class.getClassLoader(),那么代理对象会创建失败。
- interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口。 代理会通过实现接口的方式拥有该接口的方法,从而可以对它进行调用。
- h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。这是真正执行代理逻辑的地方。
四、InvocationHandler
InvocationHandler是Proxy实例处理调用实现的调用处理程序实现的一个接口。没有Proxy代理实例都关联一个InvocationHandler。每当通过代理对象调用一个方法时,调用就会被转发到与这个代理绑定了的实现了InvocationHandler接口的invoke中。
/**
* proxy:代理类代理的真实代理对象 com.sun.proxy.$Proxy0
* method:我门要调用的方法的method对象
* args:调用方法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
InvocationHandler只有一个方法,该方法有三个参数
- proxy: 代理类代理的真实代理对象 com.sun.proxy.$Proxy0
- method: 我门要调用的方法的method对象
- args: 调用方法的参数
五、代理类代码
简单的使用和方法说完这里我门不妨使用ProxyGenerator.generateProxyClass将代理类输出查看代理到底做了什么。
获取代理类class文件
static void save2File() throws IOException {
try {
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", BasketballPlayers.class.getInterfaces());
FileOutputStream out = new FileOutputStream("/tmp/a.class");
out.write(classFile);
out.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
结果
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m5;
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 say(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void eat(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void work(String var1) throws {
try {
super.h.invoke(this, m5, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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("com.myl.JdkProxy.UserService").getMethod("say", Class.forName("java.lang.String"));
m4 = Class.forName("com.myl.JdkProxy.UserService").getMethod("eat", Class.forName("java.lang.String"));
m5 = Class.forName("com.myl.JdkProxy.UserService").getMethod("work", 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());
}
}
}
可以看到,生成的代理类就是上面说的,实现了UserService接口(我们需要被代理的类的接口)和继承了Proxy类。其中的m1...m5这些method对象就是我门接口中定义的方法和基本的toString方法和equal方法。通过反射获取。我们调用say方法时,真正执行这些方法时调用了super.h.invoke(this, m3, new Object[]{var1})。
h就是我门传入的InvocationHandler的实现类。最终调用到实现类的方法从而实现在执行真正的逻辑前后进行操作。
其中三个参数
this就是将当前代理类的对象传入(如果我们有的方法是处理完逻辑返回的是当前对象,我们在InvocationHandler中就可以返回这个proxy代理对象,否则返回的对象就成了我们的被代理对象,再使用返回的对象执行其他方法就不会走代理逻辑了)
m3就是我们要调用的method对象
new Object[]{var1}为该方法参数。