cglib 入门前篇

659 阅读7分钟

cglib 是一个功能强大、高性能、高质量的字节码操作库,主要用于在运行时扩展 Java 类或者根据接口生成对象。

cglib 在一些开源框架中使用很广泛,比如 Spring, 数据库开源库 Hibernate,以及测试框架 mockito。正是因为 cglib 把脏活累活都干了,这些框架使用才很方便。

这是一个开源的库,cglib 本身的实现基于 asm 库。相比于 asm 库,cglib 的接口更友好,使用起来更简单。

下面介绍 cglib 的主要接口和类以及基于 cglib 的动态代理实现。

💡本文基于 OpenJDK11

1. Enhancer

首先要说到的就是 Enhancer 这个类,这个是 cglib 中使用的最多的类。之前 JDK 中使用反射来实现动态代理时,必须要基于接口来生成动态代理类,而 Enhancer 可以直接基于类来代理类。

Enhancer 可以生成被代理类的子类,并且拦截所有方法的调用,也就是通常所说的增强

需要注意,Enhancer 不能增强构造函数,也不能增强被 final 修饰的,或者被 static 和 final 修饰的方法

如果不想直接生成一个对象,cglib 也可以生成一个 Class 对象,用这个 Class 对象生成对象或者其他操作。

Enhancer 的使用分为两步,传入目标类型,设置回调。支持不同类型回调是 cglib 最强大的地方。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
//enhancer.setInterfaces(HelloImpl.class.getInterfaces()); // 也可以使用接口
enhancer.setCallback(...); // 设置回调

在 Enhancer 创建一个代理类之后所实现的行为要通过这些回调来实现。

常见的的回调类型如下:

  • FixedValue:返回一个固定的值

  • InvocationHandler:增强方法,添加额外的功能

  • MethodInterceptor:与 InvocationHandler 功能类似,但是控制的权限更多

  • LazyLoader:可以延迟加载被代理的对象,而且每个对象只会被创建一次

  • Dispatcher:与 LazyLoader 功能基本相同,但是在获取对象时,每次都会创建不同的对象

  • ProxyRefDispatcher:与 Dispatcher 功能类似,但是会多一个被代理对象的参数

  • NoOp:什么也不做

FixedValue

对于 FixedValue 类型的回调,调用所有的方法都会返回这个固定的值,如果类型不匹配,就会报错。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
        System.out.println("Hello cglib");
        return "Hello cglib";
    }
});

HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray"); // 会打印 Hello cglib

除了 static 和 final 类型的方法,其他所有的方法都会执行上面的代码,打印 Hello cglib。但是需要注意的是,如果某个方法返回的类型和上面的代理行为不一致就会报错,java.lang.Object 中的方法也是一样。

proxy.hashCode(); // java.lang.ClassCastException

InvocationHandler

看到这个是不是很熟悉,这个与 JDK 反射自带的 InvocationHandler 基本一致。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
        if (method.getReturnType() == String.class) {
            System.out.println("Hello cglib");
            return "Hello cglib";
        } else {
            System.out.println("Invoke other method");
            return method.invoke(proxy, objects); // 这里可能会出现无限循环
        }
    }
});

HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray");

只想改变其中部分方法的行为,其他的方法还的行为不变,最简单的思路就是如果不是目标方法就会调用本来的实现,假设要调用 hashcode() 方法:

proxy.hashCode();

这个代码并不会正常返回结果,而是进入无限循环,这是因为这个代理对象的每一个可以被代理的方法都被代理了,在调用被代理了的方法时,会重复进入到 InvocationHandler#invoke 这个方法中,然后进入死循环。

解决办法如下。

MethodInterceptor

在 MethodInterceptor 中,有一个 MethodProxy 参数,这个就可以用来执行父类方法。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
        if (method.getReturnType() == String.class) {
            System.out.println("Hello cglib");
            return "Hello cglib";
        } else {
            return methodProxy.invokeSuper(o, params); // 对上面无限问题的改善
        }
    }
});

HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray");
proxy.hashCode(); // 这个时候,调用 hashcode 方法就可以正常工作了

LazyLoader

用来延迟加载对象。

在下面这个例子中,使用 LazyLoader 来延迟加载一个 ArrayList 对象。

public class HelloImpl implements Hello {

    private ArrayList<String> data;

    public ArrayList<String> getData() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ArrayList.class);
        enhancer.setCallback(new LazyLoader() {
            @Override
            public Object loadObject() throws Exception {
                System.out.println("Begin invoke lazyloader");
                ArrayList<String> data = new ArrayList<>();
                data.add("hello");
                data.add("cglib");
                System.out.println("End invoke lazyloader");
                return data;
            }
        });

        return (ArrayList<String>) enhancer.create();
    }
}

HelloImpl helloImpl = new HelloImpl();
ArrayList<String> data = helloImpl.getData(); // 在调用这个方法的时候,ArrayList 不会被创建
System.out.println(data.get(0));
System.out.println(data.get(1)); // 每次调用都使用同一个对象,不会重复创建对象

上面代码的执行结果如下:

Begin invoke lazyloader
End invoke lazyloader
hello
cglib

在执行 data.get(0) 的时候,ArrayList 才会被创建,也就是说只有在被使用的,才会去创建对象,而且每个对象只会被创建一次。

Dispatcher

Dispatcher 与 LazyLoader 的不同之处在于,每次去获取对象的时候都会创建一个新的对象,而不是复用同一个对象。

public class HelloImpl implements Hello {

    private ArrayList<String> data;

    public ArrayList<String> getDataDispatcher() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ArrayList.class);
        enhancer.setCallback(new Dispatcher() {
            @Override
            public Object loadObject() throws Exception {
                System.out.println("Begin invoke dispatcher");
                ArrayList<String> data = new ArrayList<>();
                data.add("hello");
                data.add("cglib");
                System.out.println("End invoke Dispatcher");
                return data;
            }
        });
        return (ArrayList<String>) enhancer.create();
    }
}

HelloImpl helloimpl = new HelloImpl();
ArrayList<String> data = helloimpl.getDataDispatcher();
System.out.println(data.get(0));
System.out.println(data.get(1));// 每次调用都会获取不同送的对象

上面代理的执行结果如下,每次使用对象都会创建一个新的对象。

Begin invoke dispatcher
End invoke Dispatcher
hello
Begin invoke dispatcher
End invoke Dispatcher
cglib

ProxyRefDispatcher

ProxyRefDispatcher 与 Dispatcher 功能类似,但是多了一个参数,使用这个回调同样要注意无限循环的问题。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new ProxyRefDispatcher() {
    @Override
    public Object loadObject(Object proxy) throws Exception {
        System.out.println("Invoke");
        return proxy.hashCode(); // 同样可能会导致无限循环
    }
});

HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.hashCode(); // 这样调用会无限循环

NoOp

这个回调什么也不做,会完全继承被代理类的功能,所以 NoOp 不能使用接口来创建代理,只能使用类。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
//enhancer.setInterfaces(HelloImpl.class.getInterfaces()); //不能使用接口,只能使用类
enhancer.setCallback(new NoOp() {
});

Hello proxy = (Hello) enhancer.create();
proxy.sayHello("Ray");

这个回调在一些特定的情况下还是挺有用的,看下面的例子。

CallbackFilter

在上面的例子中,都是给 Enhancer 设置了单个回调,其实每个 Enhancer 可以设置多个回调,这里就需要用到 CallbackFilter

比如现在就需要对 sayHello 方法进行处理,其他的方法保持父类的行为就可以,按照这个要求,实现的 CallbackFilter 如下:

public class CallbackHelperImpl extends CallbackHelper {
    public CallbackHelperImpl(Class superclass, Class[] interfaces) {
        super(superclass, interfaces);
    }

    @Override
    protected Object getCallback(Method method) {
        if (method.getName() == "sayHello") { // 对这个方法就行增强,其他的方法不改变
            return new FixedValue() {
                @Override
                public Object loadObject() throws Exception {
                    System.out.println("Hello cglib");
                    return "Hello cglib";
                }
            };
        } else { // NoOp 就可以在这种情况下使用
            return new NoOp() {
            };
        }
    }
}

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
CallbackHelper callbackHelper = new CallbackHelperImpl(HelloImpl.class, null);
enhancer.setCallbackFilter(callbackHelper);
enhancer.setCallbackTypes(callbackHelper.getCallbackTypes());
enhancer.setCallbacks(callbackHelper.getCallbacks());

HelloImpl proxy = (HelloImpl) enhancer.create();

proxy.sayHello("Ray"); 
proxy.hashCode(); // 其他方法的调用不受影响

调用结果如下,只有 sayHello 方法会被拦截,其他的方法不会有变动。

Hello cglib
1647766367

2. 实现动态代理

上面介绍了 Enhancer 之后,实现动态代理应该就不难了。

InvocationHandler 和 MethodInterceptor 都可以用来实现动态代理,下面是两种实现。

InvocationHandler 实现

public class CGlibHelloProxyInvocationHandler implements InvocationHandler {

    private Object target;

    public Object bind(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(obj.getClass().getInterfaces());
        //enhancer.setSuperclass(obj.getClass().getSuperclass());
        this.target = obj;
        enhancer.setCallback(this);
        return enhancer.create(); // 生成代理类
    }

    @Override
    public Object invoke(Object o, Method method, Object[] params) throws Throwable {
        long begin = System.currentTimeMillis();
        Object result = method.invoke(target, params);
        System.out.printf("Invoke time " + (System.currentTimeMillis() - begin) + " ms");
        return result;
    }
}

使用上面的实现来创建代理并调用方法:

HelloImpl helloImpl = new HelloImpl();

CGlibHelloProxyInvocationHandler helloProxyInvocationHandler = new CGlibHelloProxyInvocationHandler();
Hello proxy = (Hello) helloProxyInvocationHandler.bind(helloImpl);

proxy.sayHello("ray");

这里需要注意,如果使用接口创建代理对象,第一行代码使用 Hello helloImpl = new HelloImpl() 或者 HelloImpl helloImpl = new HelloImpl() 创建对象传入都可以,如果使用是父类创建代理对象,那么只能使用第二种。

MethodInceptor 实现

public class CGlibHelloProxyMethodInterceptor implements MethodInterceptor {

    private Object target;

    public Object bind(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(obj.getClass().getInterfaces());
        //enhancer.setSuperclass(obj.getClass());
        this.target = obj;
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
        long begin = System.currentTimeMillis();
        Object result = method.invoke(target, params);
        System.out.printf("Invoke time " + (System.currentTimeMillis() - begin) + " ms");
        return result;
    }
}

然后创建代理对象并调用:

HelloImpl hello = new HelloImpl();

CGlibHelloProxyMethodInterceptor cGlibHelloProxy = new CGlibHelloProxyMethodInterceptor();
HelloImpl proxyObject = (HelloImpl) cGlibHelloProxy.bind(hello);

proxyObject.sayHello("ray");

通常来说,使用 MethodInceptor 方式来实现更好,因为可以避免出现上面说到的无限循环的问题。

cglib 相比于 Java 反射实现动态代理的优势就是不受类的限制,可以自由的选择根据接口或者类来生成新的代理对象。

cglib 中的 Proxy

在 JDK 中,动态代理主要由 java.lang.reflect.Proxy 来实现,在 cglib 中,同样也实现了 Proxy,功能于 JDK 中的功能基本一致,其中用到的 InvocationHandler 就是前面介绍的回调。

这样做是为了 JDK1.3 以前的版本也能使用动态代理的功能。

3. 总结

这篇文章主要介绍了 cglib 的如果通过 Enhancer 去生成代理类,可以同时支持接口子类的两种方式**。**同时也介绍了通过 Enhancer 来实现动态代理。

cglib 的能力远不止这些,下篇文章将介绍 cglib 的其他功能。

REF

[1] dzone.com/articles/cg…

文 / Rayjun