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)
*/
10public 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来指明每个拦截方法的回调类型下标。你可以 关注公众号:程序零世界 下载完整的样例代码。

总结

CGLIB是一个强大的高性能的代码生成库。作为JDK动态代理的互补,它对于那些没有实现接口的类提供了代理方案。在底层,它使用ASM字节码操纵框架。本质上来说,CGLIB通过产生子类覆盖非final方法来进行代理。它比使用Java反射的JDK动态代理方法更快。CGLIB不能代理一个final类或者final方法。通常来说,你可以使用JDK动态代理方法来创建代理,对于没有接口的情况或者性能因素,CGLIB是一个很好的选择。

如果本文有写的不对的地方欢迎评论指导,还可以关注公众号:程序零世界 获取更多源码资料

CGLIB动态代理机制,各个方面都有写到的更多相关文章

  1. 【Java深入研究】6、CGLib动态代理机制详解

    一.首先说一下JDK中的动态代理: JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的 但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该 ...

  2. Java动态代理机制——Cglib

    上一篇说过JDK动态代理机制,只能代理实现了接口的类,这就造成了限制.对于没有实现接口的类,我们可以用Cglib动态代理机制来实现. Cglib是针对类生成代理,主要是对用户类生成一个子类.因为有继承 ...

  3. Spring源码剖析5:JDK和cglib动态代理原理详解

    AOP的基础是Java动态代理,了解和使用两种动态代理能让我们更好地理解 AOP,在讲解AOP之前,让我们先来看看Java动态代理的使用方式以及底层实现原理. 转自https://www.jiansh ...

  4. 代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  5. 大厂高级工程师面试必问系列:Java动态代理机制和实现原理详解

    代理模式 Java动态代理运用了设计模式中常用的代理模式 代理模式: 目的就是为其他对象提供一个代理用来控制对某个真实对象的访问 代理类的作用: 为委托类预处理消息 过滤消息并转发消息 进行消息被委托 ...

  6. 基于 CGLIB 库的动态代理机制

    之前的文章我们详细的介绍了 JDK 自身的 API 所提供的一种动态代理的实现,它的实现相对而言是简单的,但是却有一个非常致命性的缺陷,就是只能为接口中的方法完成代理,而委托类自己的方法或者父类中的方 ...

  7. Java中如何实现代理机制(JDK动态代理和cglib动态代理)

    http://blog.csdn.net/skiof007/article/details/52806714 JDK动态代理:代理类和目标类实现了共同的接口,用到InvocationHandler接口 ...

  8. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  9. 代理模式 & Java原生动态代理技术 & CGLib动态代理技术

    第一部分.代理模式  代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常 ...

随机推荐

  1. python pip下载设置

    安装命名为 pip install -i 网址 所需要安装的库名例如:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests ...

  2. P4015 运输问题 最大/最小费用最大流

    P4015 运输问题 #include <bits/stdc++.h> using namespace std; , inf = 0x3f3f3f3f; struct Edge { int ...

  3. 存储系列之 Linux ext2 概述

    引言:学习经典永不过时. 我们之前介绍过存储介质主要是磁盘,先介绍过物理的,后又介绍了虚拟的.保存在磁盘上的信息一般采用文件(file)为单位,磁盘上的文件必须是持久的,同时文件是通过操作系统管理的, ...

  4. poj2762 判断一个图中任意两点是否存在可达路径 也可看成DAG的最小覆盖点是否为1

      Going from u to v or from v to u? Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 179 ...

  5. Web Scraper——轻量数据爬取利器

    日常学习工作中,我们多多少少都会遇到一些数据爬取的需求,比如说写论文时要收集相关课题下的论文列表,运营活动时收集用户评价,竞品分析时收集友商数据. 当我们着手准备收集数据时,面对低效的复制黏贴工作,一 ...

  6. 07.django日志配置

    https://docs.djangoproject.com/en/3.0/topics/logging/ https://yiyibooks.cn/xx/python_352/library/log ...

  7. Docker 错误:network xxx id xxxx has active endpoints

    问题描述:Docker Compose 部署的项目,使用docker-compose down 命令关闭时,提示错误: Removing network xxxl_default ERROR: net ...

  8. CPU上下文切换以及相关指标的理解

      前言 上下文切换这个词一直不理解,看了无数遍就忘了无数遍,知道看到<操作系统导论>这本书,终于有了略微的理解.这也证明了我的方向是没错的,一直认为做运维还是得理解底层的知识,不理解很多 ...

  9. {dede:channelartlist} 改变偶数的class

    {dede:channelartlist} <div {dede:global.itemindex runphp='yes'} if((@me %2) == 0){ @me = 'class=& ...

  10. Docker入门 安装Tomcat以及报404解决方案

    时间:2020/1/18 17:34:09 浏览:24 来源:互联网 记录简单的在Docker 上安装Tomcat 首先我是在云服务器上(Centos系统)安装的Docker,我们需要在https:/ ...