java动态代理深入解析

wangjianme

贡献于2016-12-28

字数:5619 关键词: Java开发

标题:Java动态代理深入详解 声明: 原创 :一叶知秋(549051701) 版本归作者及作者所属公司所有 1:JDK的动态代理为什么必须要使用接口 JDK的代理Proxy必须要使用接口,才可以实现对方法的拦截。为什么呢?因为动态代理,相当于于一个中介公司,中介公司所代理的对象是一群拥有相同规则的对象,如房产中介公司,用于代理房东出租房子。而房东是一个抽象概念,如果张三是房东,则张三必须要有房子出租。 先让我们看一个JDK动态代理的示例: 接口类: public interface IPerson { public void sayHi(String nm); } 接口实现类: public class Person implements IPerson{ public Person(){ } private String name; public Person(String name){ this.name=name; } public void sayHi(String str){ System.err.println(name+" Hello :"+str); } } 使用JDK代理Person类的方法: @Test public void testJdkProxy() throws Exception{ final Dog dog = new Dog(); //以下必须返回接口,因为Proxy内部,会根据接口创建一个IAnimal的子类, //创建的这个子类是Dog类的兄弟关系。子类可以转换成父类,但对于平行的两个转换将失败, //这就是为什么必须要使用接口的原因 IAnimal dog2 = (IAnimal)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), dog.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.err.println("Before..."); Object o = method.invoke(dog, args); return o; } }); dog2.eat(); boolean boo = Proxy.isProxyClass(dog2.getClass()); System.err.println("是否是被代理对象:"+boo+","+dog2.getClass()); } 代码说明:Proxy返回的对象必须要强转为Ianimal的类型。如果要转成Dog类型则会抛出ClassCastException。 Proxy代理可以使用以下简图加以说明: 上图中,最关键,也是最重要的部分就是Proxy在接收到Person类的实例以后,创建了IPerson类的一个子类取名为$Proxy$,此时$Proxy$类与Person类为同级兄弟关系。所以如果使用以下代码将会抛出ClassCastException: Person person = (Person)Proxy.newInstance(…..); 但使用接口就不会出现异常,即: IPerson peson = (IPerson)Proxy.newInstance(…..); 1、为了更加清楚为什么会抛出转换异常,我再做如下测试: 声明一个IAnimal接口类: public interface IAnimal { IAnimal接口 public void eat(); } 实现IAnimal接口的Dog类: public class Dog implements IAnimal { private String food; Cat实现类 Dog实现类 public Dog(String food){ this.food=food; } public void eat() { System.err.println("小狗吃:"+food); } } 实现IAnimal接口的Cat类: public class Cat implements IAnimal { private String food; public Cat(String food){ this.food=food; } public void eat() { System.err.println("小猫吃:"+food); } } 测试是否可以将Dog类的实例转换成Cat,转换时,将会抛出ClassCastException: IAnimal dog = new Dog("骨头"); IAnimal cat = new Cat("小鱼"); Dog dog2 = (Dog) cat;//cat与dog是兄弟关系不能转换成功,抛出ClassCastException     dog = cat; //此时将转换成功,因为dog是IAnimal类型,可以指向自己的子类 2、然后再测试使用反射 IAnimal dog = new Dog("骨头"); IAnimal cat = new Cat("小鱼"); boolean boo = dog.getClass()==cat.getClass(); System.err.println(boo); Method m = dog.getClass().getMethod("eat");//从dog中获取 m.invoke(cat);//不成功,说明反射从哪儿获取方法,就应该调用谁的实例 //但如果是从最高接口中获取到的方法,则可以执行,如下: Method m2 = IAnimal.class.getMethod("eat"); m2.invoke(dog);//执行成功 System.err.println("---"); m.invoke(cat);//执行成功 上例中: Method m = dog.getClass.getMethod(“eat”); m.invoke(dog);只可以执行dog的方法,如果填入cat则会执行不成功。因为Dog类拥有自己的字节码。 而如果修改成: Method m = IAnimal.class.getMethod(“eat”); m.invoke(dog); m.invoke(cat);//两个调用都可以执行成功因为,Dog和Cat拥有相同的父类接口,而IAnimal字节码只有一份。 2、使用CGLIB动态代理 Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类。使用CGLIB即使被代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理: 使用CGLIB需要导入以下两个jar文件: asm.jar – CGLIB的底层实现。 cglib.jar – CGLIB的核心jar包。 CGLIB的核心类: net.sf.cglib.proxy.Enhancer – 主要的增强类 net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现 net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用: Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。 费话少说,上代码: 1、使用CGLIB的代理: 以下测试代理一个没有实现任何接口的Person类: @Test public void testProxy1() throws Exception { final Person p1 = new Person();   //Person类没有实现任何接口 Enhancer en = new Enhancer();    //声明增加类实例 en.setSuperclass(Person.class);   //设置被代理类字节码,CGLIB根据字节码生成被代理类的子类 en.setCallback(new MethodInterceptor() {    //设置回调函数,即一个方法拦截 public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object o = method.invoke(p1,args);    //注意参数p1,仍然为外部声明的源对象,且Method为JDK的Method反射 System.err.println("After..."); return o; } }); Person p = (Person) en.create();    //通过create方法返回Person类的代理 System.err.println(p.getClass());//被代理的对象 p.sayHi("Hello"); } 2、以下测试代理一个拥有接口的类: IAnimal是接口,Dog是实现类,具体代码如下: @Test public void testProxy2() throws Exception { final Dog dog = new Dog(); //声明被代理对象 Enhancer en = new Enhancer(); //声明CGLIB增强类 en.setSuperclass(IAnimal.class); //设置接口类,也可以设置成dog实现类,会影响create返回的对象 en.setCallback(new MethodInterceptor() { public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.err.println("Before..."); Object o = method.invoke(dog, args); System.err.println("After..."); return o; } }); //Dog dog2 = (Dog) en.create();//必须转型为接口,否则抛出ClassCastException IAnimal dog2 = (IAnimal)en.create(); dog2.eat(); } 说明: 由于上例中,设置了en.setSuperclass(IAnimal.class),所以en.create()方法,返回的对象,必须要转换成IAnimal接口。如果转换成Dog则会抛出ClassCastException。 3、将CGLIB再做一个简单的包装: class CglibProxy implements MethodInterceptor{ private Object srcTarget; private CglibProxy(Object o){ this.srcTarget = o; } @SuppressWarnings("unchecked") public static T proxyTarget(T t){ Enhancer en = new Enhancer(); en.setSuperclass(t.getClass()); en.setCallback(new CglibProxy(t)); T tt = (T) en.create(); return tt; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.err.println("拦截前..."); Object o = method.invoke(srcTarget, args); System.err.println("拦截后...."); return o; } } 包装以后的调用代码如下,主要是快速的实现获取被代理类: Person p = CglibProxy.proxyTarget(new Person()); p.sayHi("HJello"); IAnimal dog = CglibProxy.proxyTarget(new Dog()); dog.eat(); 4、使用静态方法代理一个没有接口的对象 以下代码,包含在一个测试方法或是main方法中运行: final Person src = new Person(); //直接使用静态方法代理一个对象 Person p = (Person) Enhancer.create(Person.class,new MethodInterceptor(){ public Object intercept(Object proxyedObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.err.println("Hello"); //使用原生的方法调用,注意里面的src //Object oo = method.invoke(src, args); //使用MethodProxy调用父类的代码,同样有效 Object oo = proxy.invokeSuper(proxyedObj, args); return oo; } }); System.err.println(p.getClass()); p.abc(); 3:总结 1:对类的增强一般使用两种方式 一是包装,如C3p0连接池和dbcp连接池用的都是包装。包装的特点是代码量很大但运行速度快。 二是使用代理,如Spring中大量使用动态代理。动态代理的本质就是反射,所以,它的特点是代码量少但运行速度慢。不过,使用cglib代理可以解决运行速度慢的问题。 2:动态代理一般使用两种 一是jdk的proxy动态代理。它的特点是被代理的类,必须要拥有接口。 二是使用cglib由于它的低层是asm所以,它不需要被代理类有接口。同时运行速度方面也比jdk的proxy快。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ]
1 人已下载

下载文档

相关文档