cglib动态代理
之前我们说了jdk动态代理, 今天来看看cglib动态代理. 文章的最后我们对比一下jdk动态代理和cglib动态代理
首先创建一个业务类, 其中含有一个select方法.
public class InfoService {
public void select() {
System.out.println("com.nikola.demo.service.InfoService.select");
}
}
之后创建一个cglib动态代理对象的获取工厂. 当然你也可以不这么去做, 下面这种方式更加实用.
对外提供getProxyInstance, 传入委托类对象, 返回该委托类的代理对象.
public final class CglibProxyFactory {
private CglibProxyFactory() {}
/**
* 获取代理类实例
*/
@SuppressWarnings("unchecked")
public static <T> T getProxyInstance(Class<T> c) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(c);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println(String.format("=====> begin %s", method.getName()));
Object res = methodProxy.invokeSuper(o, objects);
System.out.println(String.format("=====> end %s, result %s", method.getName(), res));
return res;
});
return (T) Optional.of(enhancer.create()).orElseThrow(() -> new RuntimeException("cannot get proxy class!!!"));
}
}
最后测试一下上面的代码.
InfoService infoServiceProxy = CglibProxyFactory.getProxyInstance(InfoService.class);
System.out.println("生成代理对象: " + infoServiceProxy);
infoServiceProxy.select();

可以看到, cglib如jdk动态代理一样会生成一个代理对象. 之后调用方法, 实际是执行了MethodInterceptor中的逻辑, 我们可以看到父类的公共方法也同样走了这样的逻辑, 这个接下来我们会慢慢探究. 这个不是我想要的啊?!
从感觉上cglib相比较jdk动态代理要方便很多, 首先我们不用规定必须有一个公共接口. cglib可以直接代理一个类. 其次cglib的操作封装在Enhance中, 直接对其进行操作可以完成整个代理过程. 这比jdk动态代理, 需要通过Proxy创建代理对象, 并传入InvocationHandler调度器, 要容易理解的多.
Enhancer是什么
上面我们通过Enhancer实现了一个cglib动态代理的案例, 并且产生了一些疑问. 本节来探究原因.
Enhancer用于生成动态子类, 以实现方法拦截. 这个类一开始是作为jdk1.3标准动态代理的替代品, 但是它除了可以动态代理接口的实现, 还可以直接代理一个类(我是替补, 但我很牛B). 动态代理生成的子类会重写父类的非final类型的方法, 并且有通过钩子方法(即callback方法)回调自定义的拦截器实现.
最原始且通用的回调类型(拦截器)是MethodInterceptor(也就是我们例子中回调方法的传参). MethodInterceptor在AOP术语中允许实现环绕式增强(一般也叫通知, 增强更加准确, 灵感或许就来自Enhancer). 也就是说你可以在调用父类方法之前或者之后执行自定义的代码.另外你也可以在父类方法执行之前修改参数, 或者不去调用它.
虽然MethodInterceptor足以满足各种拦截需要, 但杀鸡焉用牛刀. 考虑到性能和简洁, 可以使用专门的回调类型, 比如LazyLoader. 通常一个增强类只有一个回调, 但是你可以通过使用CallbackFilter来控制每个方法使用哪种回调.
指定回调, CallbackFilter
在jdk动态代理中, 如果想要指定代理哪一个方法我们可能需要在invoke中做方法名的比较判断. 而cglib动态代理可以通过CallbackFilter来控制每个方法使用哪种回调.
比如: 委托类有如下两种方法.
public class InfoService {
public void select() {
System.out.println("demo.service.InfoService.select");
}
public void update() {
System.out.println("demo.service.InfoService.update");
}
}
接下来我们设置回调器, 及通过方法名指定回调器索引.
@Test
public void testCallbackFilter() {
// 两种类型的回调器, 前者使用环绕增强, 后者直接执行
Callback[] callbacks = {
(MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("====== begin");
Object object = proxy.invokeSuper(obj, args);
System.out.println("====== end");
return object;
},
(MethodInterceptor) (obj, method, args, proxy) -> {
Object object = proxy.invokeSuper(obj, args);
return object;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(InfoService.class);
// 注意多个回调器使用setCallbacks设置
enhancer.setCallbacks(callbacks);
// 设置过滤策略 如果是update方法使用环绕增强, 否则直接执行
enhancer.setCallbackFilter(method -> {
if ("update".equals(method.getName())) {
return 0;
}
return 1;
});
InfoService infoServiceProxy = (InfoService) enhancer.create();
infoServiceProxy.select();
infoServiceProxy.update();
}
接下来我们看看执行结果:
demo.service.InfoService.select
====== begin
demo.service.InfoService.update
====== end
Enhance的create方法
这一节我们看一下create方法的源码.
public Object create() {
// classOnly表示只返回类对象而非实例对象
classOnly = false;
argumentTypes = null;
return createHelper();
}
private Object createHelper() {
// 前置校验
preValidate();
// 根据之前设置的参数生成一个key, 当然如你所料这个key其实是用于获取缓存中已经有的代理对象的.
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
// 如果缓存中不存在ClassLoaderData, 则使用同步方式生成, 并放入.
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
// 加锁后重新获取, 并判断, 防止其他线程生成了ClassLoaderData后, 当前线程再次生成.
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
// 获取生成的代理类对象, 缓存中不存在则生成
// getUseCache()用于获取系统属性cglib.useCache的值, 默认为true
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
下面看一下data.get(this, getUseCache());, 根据是否使用缓存, 分别执行两种逻辑
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
return gen.generate(ClassLoaderData.this);
} else {
// 使用缓存则从缓存LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses中获取
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}
首先看一下不使用缓存的情况:
gen.generate(ClassLoaderData.this);中的逻辑
protected Class generate(ClassLoaderData data) {
Class gen;
// 获取上一次的对象... CURRENT为ThreadLocal类型
Object save = CURRENT.get();
// 存入当前调用该方法的AbstractClassGenerator对象
CURRENT.set(this);
try {
ClassLoader classLoader = data.getClassLoader();
if (classLoader == null) {
throw new IllegalStateException("ClassLoader is null while trying to define class " +
getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " +
"Please file an issue at cglib's issue tracker.");
}
// 装载类实际需要的是classLoader, 不同的classLoader装载的是不同的. 多线程下生成类名, 可以减轻锁的重量吧?
// 因为classLoader的这种特点没有必要都让所有的都锁住.
synchronized (classLoader) {
// 生成一个代理类名称, 没有指定命名策略则使用默认的DefaultNamingPolicy
String name = generateClassName(data.getUniqueNamePredicate());
System.out.println(String.format("==== 生成代理类名称为: %s", name));
// 为了保证生成的名字唯一每次生成之后, 都需要添加到Set集合中, 保证之后的判断
data.reserveName(name);
this.setClassName(name);
}
if (attemptLoad) {
try {
// 生成名字之后尝试加载有没有这个类, 如果存在则直接返回
// TODO attemptLoad不知道是干什么的, 生成名字会重复吗? 不会!
// 难道是用户在相同路径下自定义了一个和生成名字相同的类文件, 优先加载该类?
gen = classLoader.loadClass(getClassName());
return gen;
} catch (ClassNotFoundException e) {
// ignore
}
}
// strategy二进制字节码生成策略,
// 默认为DefaultGeneratorStrategy, 实现自GeneratorStrategy
byte[] b = strategy.generate(this);
// 获取类名...
String className = ClassNameReader.getClassName(new ClassReader(b));
System.out.println(String.format("==== 获取类名为: %s", className));
// 设置访问作用域 默认使用ReflectUtils的protectionDomain
ProtectionDomain protectionDomain = getProtectionDomain();
synchronized (classLoader) { // just in case
if (protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, classLoader);
} else {
gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
}
}
return gen;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
} finally {
CURRENT.set(save);
}
}
你应该注意到byte[] b = strategy.generate(this);这个方法就是生成字节码的核心了.
这里就介绍默认的生成策略DefaultGeneratorStrategy, 自定义生成策略需要实现GeneratorStrategy.
public byte[] generate(ClassGenerator cg) throws Exception {
DebuggingClassWriter cw = getClassVisitor();
transform(cg).generateClass(cw);
return transform(cw.toByteArray());
}
protected byte[] transform(byte[] b) throws Exception {
return b;
}
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
return cg;
}
其实默认的生成策略其实啥都没干, 实际上还是ClassGenerator调用了自身的generateClass方法.
不过, 如此也给我们自定义一个生成策略提供了一些启发. 我们可以在生成之前或者生成之后根据自己的需求做一些操作.
然后我们看一下使用缓存的情况:
generatedClasses的类型为LoadingCache<AbstractClassGenerator, Object, Object>, 提供get方法根据key获取缓存数据.
public V get(K key) {
// key为AbstractClassGenerator对象, 首先生成cacheKey即第二个泛型参数
final KK cacheKey = keyMapper.apply(key);
// 之后根据KK获取实际的值, 而实际的值是存放于ConcurrentMap<KK, Object> map中的
Object v = map.get(cacheKey);
// 如果其值存在且不为FutureTask则直接返回, 否则表名value已经miss, 需要重新创建.
// 关于FutureTask需要继续看createEntry才能理解
// 在多线程情况下, 为了防止多次创建value, 使用了FutureTask进行阻塞获取
if (v != null && !(v instanceof FutureTask)) {
return (V) v;
}
return createEntry(key, cacheKey, v);
}
下面看一下createEntry
protected V createEntry(final K key, KK cacheKey, Object v) {
FutureTask<V> task;
boolean creator = false;
// v不为null, 则为FutureTask, 因为如果非FutureTask则net.sf.cglib.core.internal.LoadingCache.get已经退出
if (v != null) {
// 其他线程已经在创建实例,
// Another thread is already loading an instance
task = (FutureTask<V>) v;
} else {
// 如果v不存在, 可以理解为miss v之后第一次执行createEntry
// 第一次创建, 先创建FutureTask作为v, 并将其放入缓存map中, 用来防止其他线程进入到这个代码块执行创建
task = new FutureTask<V>(new Callable<V>() {
public V call() throws Exception {
// 执行代理类创建, 这个loader需要看ClassLoaderData的构造器, 此处的函数实际是Function<AbstractClassGenerator, Object>
// 其核心目的在于执行 gen.generate(ClassLoaderData.this)
return loader.apply(key);
}
});
Object prevTask = map.putIfAbsent(cacheKey, task);
// 放入时候, 已经存在一个task, 因为上面那行代码也是有可能被多个线程执行的.
// 因为多个线程可能同时获取到v=null, 因此task被其他线程放入也是有可能的
if (prevTask == null) {
// creator does the load
creator = true;
// 执行当前task
task.run();
} else if (prevTask instanceof FutureTask) {
// 之前放入了task, 使用原始的task相当于v != null中的逻辑
task = (FutureTask<V>) prevTask;
} else {
// 已经生成了v
return (V) prevTask;
}
}
// 构建最终结果
V result;
try {
// 阻塞, 获取生成代理类对象
result = task.get();
} catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while loading cache item", e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw ((RuntimeException) cause);
}
throw new IllegalStateException("Unable to load cache item", cause);
}
// TODO creator为false, 最终结果不放入缓存? 有点懵逼
if (creator) {
map.put(cacheKey, result);
}
return result;
}
以上使用缓存和不使用缓存的分析就告一段落.
我们继续看代理类的生成之前我们已经分析到策略类DefaultGeneratorStrategy那部分了.
继续看generateClass, 我们看他的一个实现net.sf.cglib.core.KeyFactory.Generator#generateClass.
代码有点多, 其核心思想在于通过操作asm控制代码的生成.
jdk动态代理与cglib动态代理
- cglib动态代理基于继承, 因此无法代理final方法, jdk动态代理基于接口. cglib也可以设置通过接口代理.
- cglib动态代理能实现基于方法级别的拦截处理, 需要配合CallbackFilter并提供Callbacks, 准确来说是可以在方法级别上动态指定回调
- 代理类生成上, jdk动态代理纯手撸, cglib则使用了asm
- 整体来说cglib要比前者更加灵活, 可定制化更强, 比如类名生成策略, 类生成策略, 缓存使用等等.
- 性能上, 从网上找到的资料来看, 不易比较. (毕竟也没见过什么数据)
Cglib比JDK快?
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
可以参考Cglib和jdk动态代理的区别
end
下一篇主要讲cglib的回调器, 以及其他定制操作. 本文中的todo有待进一步研究, 如果更新会发公告通知.





