译 CGLIB 动态代理
CGLIB
代理提供了一个可扩展的机制来控制被代理对象的访问,其实说白了就是在对象访问的时候加了一层封装。JDK 从 1.3 版本起就提供了一个动态代理,它使用起来非常简单,但是有个明显的缺点:需要目标对象实现一个或多个接口。假如你想代理没有接口的类呢?可以使用 CGLIB 库。
CGLIB 是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的 AOP 框架(例如 Spring AOP 和 dynaop)提供方法拦截。Hibernate 作为最流行的 ORM 工具也同样使用 CGLIB 库来代理单端关联(集合懒加载除外,它使用另外一种机制)。EasyMock 和 jMock 作为流行的 Java 测试库,它们提供 Mock 对象的方式来支持测试,都使用了 CGLIB 来对没有接口的类进行代理。
在实现内部,CGLIB 库使用了 ASM 这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。除了 CGLIB,像 Groovy 和 BeanShell 这样的脚本语言同样使用 ASM 来生成 Java 字节码。ASM 使用了一个类似于 SAX 分析器的机制来达到高性能。我们不建议直接使用 ASM,因为这样需要对 JVM 非常了解,包括类文件格式和指令集。
上图展示了 CGLIB 库相关框架以及语言之间的关系。另外提醒下,类似于 Spring AOP 和 Hibernate 这些框架它们经常同时使用 CGLIB 和 JDK 动态代理来满足各自需要。Hibernate 使用 JDK 动态代理为 WebShere 应用服务实现一个事务管理适配器;Spring AOP 则默认使用 JDK 动态代理来代理接口,除非你强制使用 CGLIB。
CGLIB API
CGLIB 库的代码量不多,但是由于缺乏文档导致学习起来比较困难。2.1.2 版本的 CGLIB 库组织如下所示:
- net.sf.cglib.core:底层字节码操作类;大部分与 ASP 相关。
- net.sf.cglib.transform:编译期、运行期的 class 文件转换类。
- net.sf.cglib.proxy:代理创建类、方法拦截类。
- net.sf.cglib.reflect:更快的反射类、C#风格的代理类。
- net.sf.cglib.util:集合排序工具类
- net.sf.cglib.beans:JavaBean 相关的工具类
对于创建动态代理,大部分情况下你只需要使用 proxy 包的一部分 API 即可。
上面已经提到,CGLIB 库是基于 ASM 的上层应用。对于代理没有实现接口的类,CGLIB 非常实用。本质上来说,对于需要被代理的类,它只是动态生成一个子类以覆盖非 final 的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比 JDK 动态代理还要快。
CGLIB 库中经常用来代理类的 API 关联图如上所示。net.sf.cglib.proxy.Callback 只是一个用于标记的接口,net.sf.cglib.proxy.Enhancer 使用的所有回调都会继承这个接口。
net.sf.cglib.proxy.MethodInterceptor 是最常用的回调类型,在基于代理的 AOP 实现中它经常被用来拦截方法调用。这个接口只有一个方法:
public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
如果 net.sf.cglib.proxy.MethodInterceptor 被设置为方法回调,那么当调用代理方法时,它会先调用 MethodInterceptor.intercept 方法,然后再调用被代理对象的方法(如下图所示)。MethodInterceptor.intercept 方法的第一个参数是代理对象,第二个、第三个参数分别是被拦截的方法和方法的参数。如果想调用被代理对象的原始方法,可以通过使用 java.lang.reflect.Method 对象来反射调用,或者使用 net.sf.cglib.proxy.MethodProxy 对象。我们通常使用 net.sf.cglib.proxy.MethodProxy 因为它更快。在 intercept 方法中,自定义代码可以在原始方法调用前或调用后注入。
net.sf.cglib.proxy.MethodInterceptor 满足了所有的代理需求,但对于某些特定场景它可能使用起来不太方便。为了方便使用和高性能,CGLIB 提供了另外一些特殊的回调类型。例如,
- net.sf.cglib.proxy.FixedValue:在强制一个特定方法返回固定值,在特定场景下非常有用且性能高。
- net.sf.cglib.proxy.NoOp:它直接透传到父类的方法实现。
- net.sf.cglib.proxy.LazyLoader:在被代理对象需要懒加载场景下非常有用,如果被代理对象加载完成,那么在以后的代理调用时会重复使用。
- net.sf.cglib.proxy.Dispatcher:与 net.sf.cglib.proxy.LazyLoader 差不多,但每次调用代理方法时都会调用 loadObject 方法来加载被代理对象。
- net.sf.cglib.proxy.ProxyRefDispatcher:与 Dispatcher 相同,但它的 loadObject 方法支持传入代理对象。
我们通常对于被代理类的所有方法都使用同样的回调(如上图 Figure 3 所示),但我们也可以使用 net.sf.cglib.proxy.CallbackFilter 来对不同的方法使用不同的回调。这种细粒度的控制是 JDK 动态代理没有提供的,JDK 中的 java.lang.reflect.InvocationHandler 的 invoke 方法只能应用于被代理对象的所有方法。
除了代理类之外,CGLIB 也可以通过 java.lang.reflect.Proxy 插入替换的方式来代理接口以支持 JDK1.3 之前的代理,但由于这种替换代理很少用,因此这里省略相关的代理 API。
现在让我们看看怎么使用 CGLIB 来创建代理吧。
简单代理
CGLIB 代理的核心是 net.sf.cglib.proxy.Enhancer 类。对于创建一个 CGLIB 代理,你最少得有一个被代理类。现在我们先使用内置的 NoOp 回调:
/**
* Create a proxy using NoOp callback. The target class
* must have a default zero-argument constructor.
*
* @param targetClass the super class of the proxy
* @return a new proxy for a target class instance
*/
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(NoOp.INSTANCE);
return enhancer.create();
}
这个方法的返回值是一个目标类对象的代理。在上面这个例子中,net.sf.cglib.proxy.Enhancer 配置了单个 net.sf.cglib.proxy.Callback。可以看到,使用 CGLIB 创建一个简单代理是很容易的。除了创建一个新的 net.sf.cglib.proxy.Enhancer 对象,你也可以直接使用 net.sf.cglib.proxy.Enhancer 类中的静态辅助方法来创建代理。但我们更推荐使用例子中的方法,因为你可以通过配置 net.sf.cglib.proxy.Enhancer 对象来对产生的代理进行更精细的控制。
值得注意的是,我们传入目标类作为代理的父类。不同于 JDK 动态代理,我们不能使用目标对象来创建代理。目标对象只能被 CGLIB 创建。在例子中,默认的无参构造方法被使用来创建目标对象。如果你希望 CGLIB 创建一个有参数的实例,你应该使用 net.sf.cglib.proxy.Enhancer.create(Class[], Object[])。该方法的第一个参数指明参数类型,第二个参数指明参数值。参数中的原子类型需要使用包装类。
使用 MethodInterceptor
我们可以将 net.sf.cglib.proxy.NoOp 回调替换成自定义的 net.sf.cglib.proxy.MethodInterceptor 来得到更强大的代理。代理的所有方法调用都会被分派给 net.sf.cglib.proxy.MethodInterceptor 的 intercept 方法。intercept 方法然后调用底层对象。
假设你想对目标对象的方法调用进行授权检查,如果授权失败,那么抛出一个运行时异常 AuthorizationException。接口 Authorization.java 如下:
package com.lizjason.cglibproxy;
import java.lang.reflect.Method;
/**
* A simple authorization service for illustration purpose.
* @author Jason Zhicheng Li ( jason@lizjason.com )
*/
public interface AuthorizationService {
/**
* Authorization check for a method call. An AuthorizationException
* will be thrown if the check fails.
*/
void authorize(Method method);
}
接口 net.sf.cglib.proxy.MethodInterceptor 的实现如下:
package com.lizjason.cglibproxy.impl;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.lizjason.cglibproxy.AuthorizationService;
/**
A simple MethodInterceptor implementation to
apply authorization checks for proxy method calls.
@author Jason Zhicheng Li ( jason@lizjason.com )
*/
public class AuthorizationInterceptor implements MethodInterceptor {
private AuthorizationService authorizationService;
/**
* Create a AuthorizationInterceptor with the given
* AuthorizationService
*/
public AuthorizationInterceptor (AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
/**
* Intercept the proxy method invocations to inject authorization check.
* The original method is invoked through MethodProxy.
* @param object the proxy object
* @param method intercepted Method
* @param args arguments of the method
* @param proxy the proxy used to invoke the original method
* @throws Throwable any exception may be thrown; if so, super method will not be invoked
* @return any value compatible with the signature of the proxied method.
*/
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {
if (authorizationService != null) {
//may throw an AuthorizationException if authorization failed
authorizationService.authorize(method);
}
return methodProxy.invokeSuper(object, args);
}
}
在 intercept 方法中,先检查授权,如果授权通过,那么 intercept 方法调用目标对象的方法。由于性能原因,我们使用 CGLIB 的 net.sf.cglib.proxy.MethodProxy 对象而不是一般的 java.lang.reflect.Method 反射对象来调用原始方法。
使用 CallbackFilter
net.sf.cglib.proxy.CallbackFilter 允许你在方法级别设置回调。假设你有一个 PersistenceServiceImpl 类,它有两个方法:save 和 load。save 方法需要进行授权检查,而 load 方法不需要。
package com.lizjason.cglibproxy.impl;
import com.lizjason.cglibproxy.PersistenceService;
/**
A simple implementation of PersistenceService interface
@author Jason Zhicheng Li ( jason@lizjason.com )
*/
public class PersistenceServiceImpl implements PersistenceService {
public void save(long id, String data) {
System.out.println(data + " has been saved successfully.");
}
public String load(long id) {
return "Jason Zhicheng Li";
}
}
PersistenceServiceImpl 类实现了 PersistenceService 接口,但这个不是必须的。PersistenceServiceImpl 的 net.sf.cglib.proxy.CallbackFilter 实现如下:
package com.lizjason.cglibproxy.impl;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
/**
An implementation of CallbackFilter for PersistenceServiceImpl
@author Jason Zhicheng Li ( jason@lizjason.com )
*/
public class PersistenceServiceCallbackFilter implements CallbackFilter {
//callback index for save method
private static final int SAVE = 0;
//callback index for load method
private static final int LOAD = 1;
/**
Specify which callback to use for the method being invoked.
@method the method being invoked.
@return the callback index in the callback array for this method
*/
public int accept(Method method) {
String name = method.getName();
if ("save".equals(name)) {
return SAVE;
}
// for other methods, including the load method, use the
// second callback
return LOAD;
}
}
accept 方法将代理方法映射到回调。方法返回值是一个回调对象数组中的下标。下面是 PersistenceServiceImpl 的代理创建实现:
...
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersistenceServiceImpl.class);
CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
enhancer.setCallbackFilter(callbackFilter);
AuthorizationService authorizationService = ...
Callback saveCallback = new AuthorizationInterceptor(authorizationService);
Callback loadCallback = NoOp.INSTANCE;
Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
enhancer.setCallbacks(callbacks);
...
return (PersistenceServiceImpl)enhancer.create();
在例子中,AuthorizationInterceptor 应用于 save 方法,NoOp.INSTANCE 应用于 load 方法。你可以通过 net.sf.cglib.proxy.Enhancer.setInterfaces(Class[])指明代理需要实现的接口,但这个不是必须的。
对于 net.sf.cglib.proxy.Enhancer,除了设置一个回调对象数组,你也可以使用 net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class[])设置一个回调类型数组。在代理创建过程中如果你没有实际的回调对象,那么这种方法非常有用。像回调对象一样,你也需要使用 net.sf.cglib.proxy.CallbackFilter 来指明每个拦截方法的回调类型下标。你可以从 http://www.lizjason.com/downloads/ 下载完整的样例代码。
总结
CGLIB 是一个强大的高性能的代码生成库。作为 JDK 动态代理的互补,它对于那些没有实现接口的类提供了代理方案。在底层,它使用 ASM 字节码操纵框架。本质上来说,CGLIB 通过产生子类覆盖非 final 方法来进行代理。它比使用 Java 反射的 JDK 动态代理方法更快。CGLIB 不能代理一个 final 类或者 final 方法。通常来说,你可以使用 JDK 动态代理方法来创建代理,对于没有接口的情况或者性能因素,CGLIB 是一个很好的选择。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 分布式存储系统基础
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论