动态代理的简单示例,直接使用Proxy或实现InvocationHandler接口

       相信许多Java开发人员都使用过动态代理,即使没有直接使用过java.lang.reflect.Proxy或实现过java.lang.reflect.InvocationHandler接口,应该也用过Spring来做过Bean的组织管理。如果使用过Spring,那大多数情况都会用过动态代理,因为如果Bean是面向接口编程,那么在Spring内部都是通过动态代理的方式来对Bean进行增强的。动态代理中所谓的“动态”,是针对使用Java代码实际编写了代理类的“静态”代理而言的,它的优势不在于省去了编写代理类那一点工作量,而是实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景之中。

       演示了一个最简单的动态代理的用法,打印一句“helloworld”,代理类的逻辑是在原始类方法执行前打印一句“welcome”。我们先看一下代码,然后再分析JDK是如何做到的

public class DynamicProxyTest{
    interface IHello{
        void sayHello();
    }
    
    static class Hello implements IHello{
        @Override
        public void sayHello(){
            System.out.println("hello world");
        }
    }
    
    static class DynamicProxy implements InvocationHandler{
        Object originalObj;
        Object bind(Object originalObj){
        this.originalObj=originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),originalObj.getClass().getInterfaces(),this);
        }
        @Override
        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文件,反编译后可以看见如下代码

package org.fenixsoft.bytecode;
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 DynamicProxyTest.IHello{
    private static Method m3;
    private static Method m1;
    private static Method m0;
    private static Method m2;
    public $Proxy0(InvocationHandler paramInvocationHandler)throws{
        super(paramInvocationHandler);
    }
    public final void sayHello()throws{
        try{
            this.h.invoke(this,m3,null);
            return;
        }catch(RuntimeException localRuntimeException){
            throw localRuntimeException;
        }catch(Throwable localThrowable){
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    //此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码
    //这3个方法的内容与sayHello()非常相似。
    static{
        try{m
            3=Class.forName("org.fenixsoft.bytecode.DynamicProxyTest $IHello").getMethod("sayHello",new Class[0]);
            m1=Class.forName("java.lang.Object").getMethod("equals",new Class[]{Class.forName("java.lang.Object")});
            m0=Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);
            m2=Class.forName("java.lang.Object").getMethod("toString",new Class[0]);
            return;
        }catch(NoSuchMethodException localNoSuchMethodException){
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }atch(ClassNotFoundException localClassNotFoundException){
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

     这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。这个例子中并没有讲到generateProxyClass()方法具体是如何产生代理类“$Proxy0.class”的字节码的,大致的生成过程其实就是根据Class文件的格式规范去拼装字节码,但在实际开发中,以byte为单位直接拼装出字节码的应用场合很少见,这种生成方式也只能产生一些高度模板化的代码。对于用户的程序代码来说,如果有要大量操作字节码的需求,还是使用封装好的字节码类库比较合适。如果读者对动态代理的字节码拼装过程很感兴趣,可以在OpenJDK的jdk/src/share/classes/sun/misc目录下找到sun.misc.ProxyGenerator的源码。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值