cglib入门前篇

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 最强大的地方。

1
2
3
4
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 类型的回调,调用所有的方法都会返回这个固定的值,如果类型不匹配,就会报错。

1
2
3
4
5
6
7
8
9
10
11
12
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 中的方法也是一样。

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

InvocationHandler

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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() 方法:

1
proxy.hashCode();

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

解决办法如下。

MethodInterceptor

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}
}

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

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

1
2
3
4
Begin invoke lazyloader
End invoke lazyloader
hello
cglib

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

Dispatcher

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}
}

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

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

1
2
3
4
5
6
Begin invoke dispatcher
End invoke Dispatcher
hello
Begin invoke dispatcher
End invoke Dispatcher
cglib

ProxyRefDispatcher

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

1
2
3
4
5
6
7
8
9
10
11
12
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 不能使用接口来创建代理,只能使用类。

1
2
3
4
5
6
7
8
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 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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() {
};
}
}
}

1
2
3
4
5
6
7
8
9
10
11
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 方法会被拦截,其他的方法不会有变动。

1
2
Hello cglib
1647766367

2. 实现动态代理

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

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

InvocationHandler 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}

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

1
2
3
4
5
6
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 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}

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

1
2
3
4
5
6
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] https://dzone.com/articles/cglib-missing-manual

文 / Rayjun

© 2020 Rayjun    PowerBy Hexo