演示一个最简单的动态代理的用法,原始的逻辑是打印一句“hello world”,代理类逻辑是在原始类的方法执行前打印一句“welcome”。我们先看一下代码,然后再分析jdk是如何做到的。
public class DynamicProxyTest
interface IHello{
void sayHello();
}
static class Hello implements IHello{
public void sayHello(){
System.out.println("hello word");
}
}
static class DynamicProxy implements
InvocationHandler{
Object originalObj;
Object bind(Object originalObj){
this.originalObj = orginalObj;
return Proxy.newProxyInstance(
orginalObj.getClass().getClassLoader(),originalObj.getClass().getInterfaces(),this
);
}
public Object invoke(Object proxy,
Method method,Object[]args
)throws Throwable{
System.out.println("welcome");
return method.invoke(originalobj,args);
}
public static void main(String args[]){
IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayhello();
}
}
运行结果如下:
welcome
hello world
上述代码中,唯一的“黑匣子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。这个方法返回一个实现了IHello的接口,并且代理了 new Hello()
实例行为的对象。跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码和显式类加载等操作。前面的步骤并不是我们关注的重点,而最后它调用了
sun.misc.ProxyGenerator.generateProxyClass()方法来挖出身材字节码的动作,这个方法可以运行时产生一个描述代理类的字节码byte[]。如果想看一看这个方法在运行时产生的代理类中写了些什么,可以在main()方法中加入下面语句:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles",true);
加入这句代码后再次运行程序,磁盘中将产生一个名为"$Proxy0.class" 的代理类Class文件,反编译后可以看见如下代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements DynmicProxy.IHello{
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler);
}
public final void sayHello(){
try{
this.h.invoke(this,m3,null);
}catch(){
}
// 省略equals hashcode tostring 等方法
}
static{
trh{
m3=Class.forName("org.fenixaoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello",new Class[0]);
.......
}catch(){
}
}
}
这个代理类的实现代码也很简单,它未传入接口中的每一个方法,以及从java.lang.Object 中继承来的 equals hashcode 等方法都生成对应的实现,
并且统一调用了InvocationHandler 对象的 invoke()方法(代码中的 ”this.h“ 就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法
的内容,各个方法的区别不过是传入的参数和Method 对象有所不同,所以无论调用动态代理的那个方法,实际上都是在执行InvocationHandler.invoke()中的
代理逻辑。
这个例子中并没有讲到generateProxyClass()方法具体是如何产生代理类"$Proxy0.calass" 的字节码,大致的生成过程其实就是根据Class文件的格式规范
去拼装字节码,但是在实际过程中,矣byte未单位直接拼接出字节码的应用场合很少见,这种生成方式也只能产生一些高度模板化的代码,对于用户的程序
代码来说, 如果有要大量操作字节码的需求,还是使用封装好的字节码类库比较合适。如果你对动态代理的字节码拼装过程确实很感兴趣,可以到OpenJDK
的jdk/src/share/classes/sun/misc 目录下找到sun.misc.ProxyGenerator 的源码