动态代理
1.动态代理
1.1jdk的动态代理
public interface Movable{
void move();
}
public class Tank implements Movable{
@Override
public void move(){
System.out.println("a tank ClaClaCla...");
try{
Thread.sleep(new Random().netInt(1000));
}catch(InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args){
Tank tank = new Tank();
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(method.getName+"a tank start moving...");
Object o = method.invoke(tank, args);
System.out.println(method.getName+"stop!");
return o;
});
m.move();
}
}
1.2 jdk动态代理执行流程
1.3 jdk动态代理原理解析
-
调用Proxy的newProxyInstance方法,传递三个参数。
第一个是类加载器,一般是代理对象的加载器;
第二个是代理对象实现的接口 Movable.class;(jdk动态代理的局限就在于此,其代理对象必须实现某个接口)
第三个是调用处理器invocationHandler的实现类,一般都是用匿名内部类实现 -
调用Proxy的newProxyInstance方法返回一个代理对象,我们用接口来接收,即Movable m = (Movable)Proxy.newProxyInstance
- 为什么可以用Movable接收
- 因为返回的代理对象实现了Movable接口
-
之后用户可以通过代理对象来执行被代理对象的功能,即 m.move();
这个方法的实质是 proxy$0.move()。 -
proxy$0是Proxy生成的代理对象,也就是m。proxy$0没有源码,它的来源是ASM,ASM直接增删.class文件中的字节生成了proxy$0。
proxy$0一般只会存在于内存,不会再生成.class文件(不过也有特殊情况,见2.1.4)ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。
-
以上说明了m.move() 其实就是 proxy$0.move(),而proxy$0.move()中除了异常处理只有一句关键代码,
那就是调用InvocationHandler实现类中的invoke()方法- 为什么可以调用?
- 调用newProxyInstance的时候给Proxy传递了三个参数,其中就有InvocationHandler实现类,而proxy$0继承了Proxy,proxy$0可以通过父类调用InvocationHandler实现类中的invoke()方法
-
来看看invoke方法 -- public Object invoke(Object proxy, Method method, Object[] args)
- 有三个参数分别为 调用该方法的代理实例、调用的接口方法对应的Method实例、参数
-
再来看看invoke方法中的内容
System.out.println(method.getName+"a tank start moving..."); Object o = method.invoke(tank, args); System.out.println(method.getName+"stop!"); return o;
两个输出语句作为日志功能增强,不作解析,重点看一下其他两行代码,
- 一个是method.invoke(tank, args),它其实就是调用tank的move方法
- 另一个是这个return,可能有人像我一样纠结这里的返回是给谁的,其实这里的返回值是m.move()的返回值
- tip:有一个好玩的地方
- tank的move方法返回值是void
- 但m.move()方法返回值Object,那么我们是可以修改return,让它返回一些数据的
- 那么就会出现
- 调用被代理对象的方法的返回值 和 调用代理实例的方法的返回值 不一样的情况
- 而我们也就可以根据这一情况来实现某些功能了,具体实现大家可以想想或者去网上找找
1.4 proxy$0的代码
-
前面有提过,proxy$0一般只会存在于内存,不会再生成.class文件
-
但是如果我们在main方法中添加下列代码,就可以让java虚拟机生成.class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
-
不知道为什么ta名字变了,大家知道$proxy0和proxy$0是同一个就好,[狗头][狗头][狗头]
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
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 proxy.movable;
public final class $Proxy0 extends Proxy implements movable {
private static Method m1;
private static Method m3;
private static Method m2;
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 void move() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 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"));
m3 = Class.forName("proxy.movable").getMethod("move");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
2.动态代理——cglib
前面提到了,jdk动态代理的局限性——被代理对象必须实现一个以上的接口。那么有没有一种没有这个局限的动态代理呢?当然是有的,那就是cglib,全名为Code Generation Library。
但是博主只是想提一下,不打算写了[狗头][狗头][狗头],大家有兴趣的话,可以自己去找一找。溜溜球~~~