译 CGLIB 动态代理

发布于 2023-07-15 16:25:08 字数 10017 浏览 38 评论 0

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

哭泣的笑容

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

13886483628

文章 0 评论 0

流年已逝

文章 0 评论 0

℡寂寞咖啡

文章 0 评论 0

笑看君怀她人

文章 0 评论 0

wkeithbarry

文章 0 评论 0

素手挽清风

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文