Java动态代理之cglib动态代理

Java动态代理之JDK动态代理中,我们了解了JDK动态代理。它的好处是随JDK自带,不需要额外导入其他包,但是它除了继承自Object类的equals(), hashCode()toString()方法之外,只能代理实现的接口中的方法,那么如果碰巧我们需要代理的类没有实现接口怎么办呢?这时候就可以使用CGLIB动态代理,它是基于Java的继承机制实现的,因此只要不是final方法或static方法都可以进行代理。

注:CGLIB官方声明已不再维护,因此如果你正在使用JDK17及更高版本可以使用ByteBuddy代替

如何使用cglib

  1. 先创建一个Dog类作为要被代理的真实类,注意该类并未实现如何接口
public class Dog {

    public void run() {
        System.out.println("狗狗在跑");
    }

    public void bark() {
        System.out.println("汪汪汪");
    }
}
  1. 创建一个实现了MethodInterceptor接口的类,在intercept方法中写要增强的逻辑
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("cglib动态代理执行中...");
        return proxy.invokeSuper(obj, args);
    }
}

intercept方法接收四个参数,分别是代理对象,被调用的方法,被调用方法的参数和一个用来帮助调用真实方法的MethodProxy对象。

MethodInterceptor的intercept方法和JDK动态代理中的InvocationHandler的invoke方法很类似:对代理对象继承的方法或是实现的接口的方法进行调用时,都会转交给它们来执行。

intercept方法参数也和invoke方法差不多,唯一的区别就是最后的MethodProxy对象。当我们执行它的invokeSuper方法时,会调用父类方法,也就是真实类的方法,这样可以避免导致产生StackOverFlowError的异常。相反,如果使用下面的方式则会产生无限递归的情况:

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    // ERROR
    System.out.println(proxy.toString());
    // ERROR
    proxy.invoke(obj, args);
    // ERROR
    return method.invoke(obj, args);
}

因为上面的错误写法都是在cglib生成的代理对象上调用方法,因此又会被转交给intercept方法执行,从而无限套娃下去。

  1. 通过Enhancer类来设置代理对象的父类,代理类需要增强的逻辑(上一步写在MethodInterceptor中的内容)和实际创建代理对象
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 设置Callback,也就是代理类需要增强的部分
        enhancer.setCallback(new MyMethodInterceptor());
        // 指定生成的代理对象的父类
        enhancer.setSuperclass(Dog.class);
        // 生成代理对象
        Dog dogProxy = (Dog) enhancer.create();
        dogProxy.bark();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>");
        dogProxy.run();
    }
}

运行结果如下:
在这里插入图片描述

多种多样的Callback

Callback是cglib提供的一个接口,我们希望cglib增强的逻辑需要写在该接口的实现类中。

前面提到的MethodInterceptor也是Callback的一个子接口,它是功能最强大,最灵活的一个接口,但相应的性能也会差一些。

除了MethodInterceptor之外,cglib还提供了多个Callback的子接口,它们没有MethodInterceptor全能,但会更加高效。

下面介绍几个常用的Callback子接口:

LazyLoader

该接口可以将对代理方法的调用转发到另一个对象身上,由另一个对象来负责处理。

还是结合一个例子来看下如何使用LazyLoader吧:

  1. 定义一个柯基类
public class Corgi extends Dog {

    @Override
    public void bark() {
        super.bark();
        System.out.println("我是一只柯基");
    }
}
  1. 实现LazyLoader接口
public class MyLazyLoader implements LazyLoader {
    @Override
    public Object loadObject() throws Exception {
        return new Corgi();
    }
}

该接口需要我们重写loadObject方法,当调用代理方法时,cglib会转发到loadObject返回的对象身上

  1. 设置callback为LazyLoader类型并测试运行
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dog.class);

        enhancer.setCallback(new MyLazyLoader());
        Dog dog = (Dog) enhancer.create();
        dog.bark();
    }
}

运行结果如下:
在这里插入图片描述

可以看到我们调用bark方法时,转为调用了柯基类的bark方法。

也许有同学会问LazyLoader和继承有什么区别:从功能上说二者区别不大,但LazyLoader只会在调用被代理的方法时才执行loadObject方法去创建对象,比较适合创建对象成本较高且不确定是否会执行方法调用的场景。

FixedValue

public interface FixedValue extends Callback {

    Object loadObject() throws Exception;
}

FixedValue接口同样也只有一个loadObject方法,并且方法签名和LazyLoader的loadObject方法一模一样,但二者的功能却完全不同。

如果将Enhancer的callback设置为FixedValue类型,那么每次调用代理方法都会转为调用loadObject方法,因此FixedValue的loadObject方法返回的对象类型一定要与原方法匹配。

NoOp

正如接口名所言,该接口除了调用原方法之外什么都不做。

听起来非常奇怪吧,为什么cglib要多此一举设计NoOp这样一个看起来没有什么作用的接口呢?

其实,它主要是和CallbackFilter配合起来使用的,用来做到方法级别的代理。

回想一下上篇文章讲的JDK动态代理和本文介绍的cglib动态代理,都会对实现的接口或者继承的父类的所有方法进行代理,那如果不想代理所有的方法怎么办呢?

比如上面讲的FixedValue接口要求loadObject的返回值要与真实方法的返回值相匹配,那不同方法的返回值类型是不同的,显然不能将FixedValue应用到所有方法上,这时就需要配合NoOp和CallbackFilter来控制在哪些方法上应用FixedValue。

还是结合例子来看下应该如何使用,假设我们只想代理Dog类的toString方法:

  1. 定义MyFixedValue类实现FixedValue接口
public class MyFixedValue implements FixedValue {
    @Override
    public Object loadObject() throws Exception {
        System.out.println("fixedValue");
        return "这是一只经过cglib代理过的狗狗";
    }
}

MyFixedValue的loadObject方法返回一个字符串,与toString的返回值类型相同,但对于Dog类的bark方法和run方法因为返回值类型不同所以不能进行代理。

  1. 定义一个CallbackFilter的实现类
public class MyCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if (method.getName().equals("toString")) {
            return 0;
        }
        return 1;
    }
}

这里返回的0或1代表使用第几个Callback进行处理,如果调用的方法为toString,则使用第一个Callback进行处理,否则使用第二个。

  1. 为Enhancer按顺序设置Callback并进行测试
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dog.class);
        // 设置CallbackFilter
        enhancer.setCallbackFilter(new MyCallbackFilter());
        // 这里设置callback的顺序要与CallbackFilter中保持一致
        enhancer.setCallbacks(new Callback[]{new MyFixedValue(), NoOp.INSTANCE});
        Dog dog = (Dog) enhancer.create();
        System.out.println(dog.toString());
        dog.bark();
    }
}

运行结果如下:
在这里插入图片描述

可见此时只有toString方法被代理了,而bark方法并没有被代理。

另外,如果想保存cglib生成的字节码,可以使用下面的语句:

// ./代表保存到当前路径
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值