1、动态代理的目的
动态代理用于代码的分离;将通用代码从各个业务模块中分离出来,不必在每个业务模块中都去实现,这样既减少了开发工作量,也便于维护;
eg: “吃饭”、“说话”这两个业务都需要用到“张嘴” “闭嘴”这两行代码,但是张嘴之后和闭嘴之前所做的事情是不一样的,这样将业务模块和通用模块(即 张嘴、闭嘴)分离出来,每次在说话 或者 吃饭的时候,使用通用模块+业务模块 实现吃饭和说话这两件事;
2、动态代理的应用
最典型的应用是基于动态代理技术实现spring框架中的AOP(面向切面编程); 面向切面编程,通过切面代码层,实现业务代码和通用代码的分离,这样各个业务模块可以共用一个通用代码。大概原理如下:切面层代码提供了一个调用其他方法的接口(姑且命名为 invoke),通过反射机制将类 和 方法等参数传入后,便在invoke 里面执行此方法,传入的方法仅仅是invoke方法的一部分,另一部分便是通用代码,这样就将通用代码和业务代码编制在了一起。(实际上sping中的增强就是这么回事)
3、动态代理的原理及实现
java提供了两种动态代理方式,一种是JDK动态代理,另一种是cglib动态代理;JDK动态代理仅仅实现了对接口的代理,cglib实现的是对对象的代理;二者相比,chlib综合性能更好一点;
(1) JDK动态代理
JDK动态代理是基于 “InvocationHandler接口” 和 “Proxy类” 实现的,其技术基础是 JAVA中的反射机制 及 面向对象中的重写;其中 代理类就是继承于InvocationHandler这个接口的,主要是继承他的invoke 方法;Proxy类用于获取反射对象,调用方法。虽然在本来的业务类中没有invoke方法,但是会在Java虚拟机中生成一个$Proxy0文件,这个文件是继承于本来的业务类,在这个文件中会调用invoke方法。
下面就根据上图中的代理模型,来写一个简单的JDK动态代理
① 首先构建业务层接口 java代码如下:
package Proxy.JDK; import java.lang.*; public interface SayHelloWorld { public void say(String language); }
② 基于构建的接口层,创建业务类
package Proxy.JDK; public class menSay implements SayHelloWorld { @Override public void say(String language) { // TODO Auto-generated method stub System.out.println("HelloWorld!"); } }
③ 构建代理类
package Proxy.JDK; import java.lang.reflect.*;; public class proxyClass implements InvocationHandler { private Object VR_class; /** * 返回代理类 * @method: proxyClass() -by fjt * @TODO: 利用反射机制,被代理业务类产生代理对象 * @param obj 被代理的业务类 * @return * @return Object */ public Object getProxy(Object obj) { this.VR_class = obj; return Proxy.newProxyInstance(VR_class.getClass().getClassLoader(), VR_class.getClass().getInterfaces(), this); } @Override /** * 当调用业务层方法的时候 就会进入这个接口,所以可以认为是覆盖了原先的方法 * 至于是怎么实现覆盖的,为什么会跳到这儿,还不是很清楚 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("张嘴"); method.invoke(this.VR_class, args); System.out.println("闭嘴"); return null; } }
④ 代理的应用
package Proxy.JDK; public class jdk_proxyTest { public static void main(String[] arges) { SayHelloWorld manSay = new menSay(); proxyClass proxyMan = new proxyClass(); //JDK实现的动态代理 是基于接口实现的,因此下面的语句 如果将SayHelloWorld 改为 menSay就会出错 //$Proxy0 cannot be cast to Proxy.JDK.menSay (因为menSay是一个类 Proxy0) SayHelloWorld proxyMenSay = (SayHelloWorld)proxyMan.getProxy(manSay); proxyMenSay.say("hi!"); } }
通过上述例子不难看出,JDK动态代理,实际上就是接口注入,也仅仅是接口注入,通过代理返回的对象也是接口,所以代理的也只能是接口。
(2) CGLIB动态代理
JDK动态代理使用起来不太方便,需要写接口、服务类、代理类等,而且只能代理接口,繁琐许多。相比之下,CGLIB就简单许多了。然而CGLIB创建代理对象的时间要花费的比JDK多。所以如果需要频繁的创建对象,并且是接口代理,使用JDK会更好一点。CGLIB是动态创建子类,所以不能代理final变量和方法。
CGLIB的使用,需要引入如下几个包:
下面先看一下CGLIB的动态代理的实现吧!
package Proxy.CGLIB; public class sayHello { public int a = 0; public int b = 0; public void add() { System.out.println("计算结果: " + Integer.toString(a+b)); } }
package Proxy.CGLIB; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { private Object obj; //被代理的原始对象 public Object creatProxy(Object target) { this.obj = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.obj.getClass()); //设置代理目标 enhancer.setCallback(this); enhancer.setClassLoader(target.getClass().getClassLoader()); return enhancer.create(); } //阻塞被代理的类的方法调用 @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { // TODO Auto-generated method stub System.out.println("调用之前"); arg3.invokeSuper(arg0, arg2); System.out.println("调用之后"); return null; } }
package Proxy.CGLIB; public class cglib_proxyTest { public static void main(String[] args) { sayHello calcu = new sayHello(); CglibProxy cglib_proxy = new CglibProxy(); sayHello cal_proxy = (sayHello)cglib_proxy.creatProxy(calcu); cal_proxy.a = 1; cal_proxy.b = 1; cal_proxy.add(); } }
看起来很简单吧。
CGLIB实现的是代理类,JDK实现的是代理接口;CGLIB是通过intercept这个函数来阻塞代理类函数的调用,也就是说调用被代理类的方法的时候,执行的实际上是intercept这个方法。