JDK的动态代理为什么必须要使用接口与使用CGLIB动态代理

一.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(),

                  newInvocationHandler() {

                     public Objectinvoke(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 {

    public Dog(String food){    public void eat();

}

实现IAnimal接口的Dog类:

public class Dog implements IAnimal {

    private 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;//catdog是兄弟关系不能转换成功,抛出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);//执行成功

上例中:

    Methodm = 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字节码只有一份。


二.使用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类的代理类,可以方便的实现对源对象方法的调用,如使用:

    Objecto = 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>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,newMethodInterceptor(){

           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();



















  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDK动态代理CGLib动态代理都是实现AOP编程的方式,它们的使用上有以下异同点: 1. JDK动态代理只能代理实现接口的类,而CGLib动态代理可以代理没有实现接口的类。 2. JDK动态代理是通过反射来实现的,而CGLib动态代理使用的是继承。 3. JDK动态代理在生成代理对象时,需要传入一个InvocationHandler对象,而CGLib动态代理在生成代理对象时,需要继承MethodInterceptor类,并重写intercept()方法。 4. JDK动态代理生成的代理对象性能比CGLib动态代理生成的代理对象性能要低。 下面是一个JDK动态代理的例子: ```python import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Subject { void request(); } class RealSubject implements Subject { public void request() { System.out.println("RealSubject request"); } } class DynamicProxy implements InvocationHandler { private Object subject; public DynamicProxy(Object subject) { this.subject = subject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before calling " + method); method.invoke(subject, args); System.out.println("after calling " + method); return null; } } public class Main { public static void main(String[] args) { RealSubject realSubject = new RealSubject(); InvocationHandler handler = new DynamicProxy(realSubject); Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler); subject.request(); } } ``` 下面是一个CGLib动态代理的例子: ```python import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; class RealSubject { public void request() { System.out.println("RealSubject request"); } } class DynamicProxy implements MethodInterceptor { private Object subject; public DynamicProxy(Object subject) { this.subject = subject; } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before calling " + method); proxy.invoke(subject, args); System.out.println("after calling " + method); return null; } } public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealSubject.class); enhancer.setCallback(new DynamicProxy(new RealSubject())); RealSubject subject = (RealSubject) enhancer.create(); subject.request(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值