动态代理总结

来源:互联网 发布:表格公式查找相同数据 编辑:程序博客网 时间:2024/05/18 20:08

什么是代理?
本来想调用目标类的方法,但是现在改成调用代理类的同名方法,在代理类的方法中,再去调用目标类的方法,并且在调用方法的前后,加上额外的一些逻辑。就酱。
代理分为静态代理和动态代理,这两种的区别是代理类生成时间的不同。
那什么是静态代理呢?静态代理就是一开始把接口、目标类、代理类都定义好了,在程序运行之前就已经存在代理类的字节码文件了。
而动态代理呢,则是利用java的反射,在运行过程中动态的生成代理类。下面讲的主要是动态代理。
动态代理具体实现:
定义两个类,一个目标类,一个代理类,这两个类实现相同的接口,这样就会有同样的方法。定义接口变量指向代理类对象,调用代理类方法时,代理类再调用目标类的方法。
这里贴一张传智播客的图:
这里写图片描述

动态代理,大体就是这么个样子。那么写代码的时候怎么写呢?java有专门实现动态代理功能的Proxy类。
接下来看段代码:

Class proxyClass = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);Collection collection = (Collection) constructor.newInstance(new InvocationHandler() {    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        return null;    }});

Proxy类的getProxyClass方法,就可以创建一个代理类。
当我们创建一个类的时候,需要知道这个类是哪个类加载器加载的,还需要知道这个类里面都有哪些方法,所以,getProxyClass方法有两个入参,第一个是类加载器,第二个参数是接口的可变参数,指定这个类实现哪些接口,也就相当于是说明了这个类都有哪些方法。
如果调用proxyClass.getName()方法,会看到打印出来的结果是:com.sun.proxy.$Proxy0,这个类是没有无参的构造方法的。
调用getProxyClass得到代理类之后,就可以创建这个代理类的实例对象了,注意这里是不能通过调用proxyClass.newInstance();来获取实例对象的,因为这样会调用无参的构造方法,而com.sun.proxy.$Proxy0这个类是没有无参的构造方法的,只有一个有参的构造方法,接受一个InvocationHandler对象。所以,需要先拿到这个代理类的构造方法,然后通过构造方法去创建实例对象。因为代理类实现了Collection接口,所以创建出的实例对象,可以看成是一个Collection。
上面这段代码通过JVM创建代理类的实例对象,用了两步,第一步,创建了一个代理类,第二步,通过代理类的构造方法,创建实例对象。其实,Proxy里面还有newProxyInstance方法,可以一步就创建出代理类的实例对象。接下来看下面这段代码:

Collection collection2 = (Collection) Proxy.newProxyInstance(        Collection.class.getClassLoader(),        new Class[]{Collection.class},        new InvocationHandler() {            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                return null;            }        });collection2.add("a");

这个方法,就把上面的两步合并成了一个方法。但是如果此时调用collection2.add(“a”);方法,会报空指针异常。这是为什么呢?这就需要了解调用collection2.add(“a”);方法的时候,都做了啥。
现在的代码,只是创建了动态类和动态类的实例对象,并没有定义目标类。所以需要定义一个目标类。

Collection collection3 = (Collection) Proxy.newProxyInstance(        Collection.class.getClassLoader(),        new Class[]{Collection.class},        new InvocationHandler() {            List<String> list = new ArrayList<String>();            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                System.out.println(proxy.getClass().getName());                System.out.println("begin");                Object result = method.invoke(list, args);                System.out.println("end");                return result;            }        });collection3.add("1");collection3.add("2");collection3.add("3");System.out.println(collection3.size());

这段代码就可以正常执行了,可以看到,在InvocationHandler对象里面,定义了一个ArrayList目标类对象,在invoke方法里面,有个method.invoke(list, args);
说下具体的执行步骤:
当调用collection3.add(“1”);方法的时候,实际上是调用了InvocationHandler中的invoke方法。而collection3.add(“1”)这行代码,有三个信息,一个是代理对象,也就是collection3,一个是调用哪个方法,也就是add,一个是传递了什么参数,也就是”1”。创建InvocationHandler实体的时候,invoke方法也有三个入参,Object proxy 代表这个代理对象,Method method 代表调用的哪个方法,Object[] args 代表传递了什么参数。
所以,调用InvocationHandler中的invoke方法,本质就是通过反射,去调用目标对象的方法。
所以现在也就知道了第二段代码中,调用collection2.add(“a”);方法时为什么会报空指针异常了。add方法需要有布尔类型的返回值的,而那段invoke方法,直接return了null,调用collection2.add(“a”)本质是调用invoke方法,所以,invoke方法也需要返回布尔值才行,如果返回了null,那么在把null转换为布尔值的时候,就会报空指针异常。