Cglib proxy探秘

2,030 阅读5分钟

  Cglib是一个非常著名的字节码修改库,广泛的应用于一些开源框架中。spring 里面的aop技术就用到了Cglib这个库。这篇文章想通过代码简单的介绍一些Cglib的使用。

1. maven 依赖

  maven 仓库里面展示了目前可用的版本,本文的依赖如下:

 <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
</dependency>

2.代理模式

  代理模式是一种比较常见的设计模式,为什么要用代理模式?代理模式可以隔离底层和调用者,可以做权限的控制。知乎上有一个关于什么是java动态代理的回答,可以参考。jdk自带的动态代理要求被代理的类实现了接口,这严重的制约了它的使用范围。cglib没有这样的限制,所以在很多地方只能使用cglib来实现动态代理。

3. 使用cglib实现代理

3.1 Hello World

  下面是一个简单的cglib实现代理的例子:

public class CglibTestExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonService.class);
        enhancer.setCallback((FixedValue) () -> "Hello Tom!");
        PersonService proxy = (PersonService) enhancer.create();

        String res = proxy.sayHello(null);
        System.out.println(res);

    }
}
class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

Enhancer是cglib用来生成proxy的类,生成的proxy类是被代理类的子类,所以可以看到enhancer.setSuperclass(PersonService.class); 当然setSuperClass也可接收interface,官方的说明如下:

Set the class which the generated class will extend. As a convenience, if the supplied superclass is actually an interface, setInterfaces will be called with the appropriate argument instead. A non-interface argument must not be declared as final, and must have an accessible constructor.

需要注意的就是如果传的不是接口,这个类一定不能是final的(无法被继承)并且一定有一个可以访问的构造函数。

3.2 CallBack

  CallBack是方法被拦截的时候调用的,这也就是为什么String res = proxy.sayHello(null); 会返回Hello Tom!FixedValue实现了CallBack这个接口,返回一个固定的值。当然实现了CallBack接口的有很多,其中有一个叫MethodInterceptor,它的功能非常的强大,可以看下面的例子:

 enhancer.setCallback((MethodInterceptor) (obj, method, arg, proxy) -> {
        if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
             return "Hello Tom!";
        } else {
        return proxy.invokeSuper(obj, arg);
    }
});

理解上面这段代码之前,我们可以先看一下MethodInterceptor.intercept的签名,

public java.lang.Object intercept(java.lang.Object obj,
                                  java.lang.reflect.Method method,
                                  java.lang.Object[] args,
                                  MethodProxy proxy)
                                throws java.lang.Throwable

参数的含义如下:

obj - "this", the enhanced object
method - intercepted Method
args - argument array; primitive types are wrapped
proxy - used to invoke super (non-intercepted method); may be called as many times as needed

  上面MethodInterceptor的含义是,如果申明被拦截方法的类名字是Object并且被拦截的方法返回的结果是String,那么就返回Hello Tom!,否则就调用被拦截的方法。可以用proxy.lengthOfName("Mary") 测试发现返回的结果是4.

  下面说明一下Callback的所有subinterface的含义

  1. NoOp

Methods using this Enhancer callback will delegate directly to the default (super) implementation in the base class.

也就是说会调用被代理类的实现

  1. Dispatcher

Dispatching Enhancer callback. This is identical to the LazyLoader interface but needs to be separate so that Enhancer knows which type of code to generate.

可以看到 DispatcherLazyLoader 可以放到一起去对比和分析,这里有一篇讲 LazyLoader文章,说的算比较清楚的了,把文章里面的例子换成 Dispatcher 就知道Diapatcher 是干啥的了,同时附上 DispatcherloadObject 方法的说明:

Return the object which the original method invocation should be dispatched. This method is called for every method invocation.

  1. LazyLoader

Dispatcher的解析

  1. FixedValue

Enhancer callback that simply returns the value to return from the proxied method. No information about what method is being called is available to the callback, and the type of the returned object must be compatible with the return type of the proxied method. This makes this callback primarily useful for forcing a particular method (through the use of a CallbackFilter to return a fixed value with little overhead.

有一点需要注意的是 FixedValue 返回的类型必须和被代理方法返回的类型兼容,还是文章最开始的例子,如果用proxy调用 lengthOfName 方法,就会报错,String无法转成Long。

3.3 CallBackFilter

  有时候我们需要根据被拦截的方法来执行不同的逻辑。MethodInterceptor提供了一定的灵活性,但是还是不够。这个时候就需要使用setCallbackssetCallbackFiltersetCallbacks会传一组CallBack,而CallBackFilter会根据被拦截的方法返回一个index,这个index会去CallBack数组里面取,下面是CallbackFilter.accept的说明。

public int accept(java.lang.reflect.Method method)
    Map a method to a callback.
Parameters:
    method - the intercepted method
Returns:
    the index into the array of callbacks (as specified by Enhancer.setCallbacks(net.sf.cglib.proxy.Callback[])) to use for the method.

下面是一个简单的例子:

enhancer.setCallbacks(new Callback[]{
                (FixedValue) () -> "0", (FixedValue) () -> 1
        });
        enhancer.setCallbackFilter((Method method) -> {
            if (method.getName().equals("sayHello")) {
                return 0;
            } else {
            return 1;
        }
    });

  可以看到如果我们调用proxy.sayHello(null),会返回字符串“0”,如果我们调用proxy.lengthOfName("abc") 会返回1。

3.3 CallbackTypes

  从doc可以看到,Callback这个接口没有定义任何的方法,那在 setCallback 方法的时候,需要知道具体传入的是Callback的哪个子接口,以便在调用Callback的时候知道具体调用哪个方法。例如:我们上面的例子中传入的是 FixedValue ,那么就会调用 loadObject。cglib 是怎么知道传入的Callback是什么类型的呢?看一下下面的代码

private void preValidate() {
        if (callbackTypes == null) {
            callbackTypes = CallbackInfo.determineTypes(callbacks, false);
            validateCallbackTypes = true;
        }
        if (filter == null) {
        if (callbackTypes.length > 1) {
            throw new IllegalStateException("Multiple callback types possible but no filter specified");
        }
        filter = ALL_ZERO;
    }
}

在cglib 里面有一个 callbackTypes 的变量,你可以显示的进行设置。如果你没有设置,cglib会根据你传入的callbacks来确定。也就是上面的 CallbackInfo.determineTypes(callbacks, false),跟踪这个方法我们可以找到这个地方,这个地方列举了Callback的所有subinterface。每一个type对应了一个Generator,在构建proxy的时候会根据不同的CallbackType,调用不同的Generator。

 private static final CallbackInfo[] CALLBACKS = {
    new CallbackInfo(NoOp.class, NoOpGenerator.INSTANCE),
    new CallbackInfo(MethodInterceptor.class, MethodInterceptorGenerator.INSTANCE),
    new CallbackInfo(InvocationHandler.class, InvocationHandlerGenerator.INSTANCE),
    new CallbackInfo(LazyLoader.class, LazyLoaderGenerator.INSTANCE),
    new CallbackInfo(Dispatcher.class, DispatcherGenerator.INSTANCE),
    new CallbackInfo(FixedValue.class, FixedValueGenerator.INSTANCE),
    new CallbackInfo(ProxyRefDispatcher.class, DispatcherGenerator.PROXY_REF_INSTANCE),
};

4.总结

  这篇文章介绍了spring在使用cglib时用到的一些方法,为之后更好的理解spring aop打下基础。