动态代理模式的核心就是“字节码重组”。
满足代理模式的三个条件:
1.执行者和被代理人
2.对于被代理人来说,这件事情一定要做,但是自己不想做,没时间去做或者自己做不到,需要找代理
3.需要获取被代理人的个人信息
cglib 动态代理示例
cglib生成动态代理类的机制——通过类继承
JDK中提供的生成动态代理类的机制有个鲜明的特点:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。例如:例子有个XiaoMing的类实现了Person接口外,另外实现了getName()方法,从而在产生动态代理类中不会有这个方法了;更极端的情况就是某个类没有实现接口,那么这个类就不能使用JDK产生动态代理了。
正好有cglib。CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
cglib创建某个类A的动态代理的模式为:
1.查找A上的所有非final的public类型定义的方法;
2.将这些方法的定义转换成字节码;
3.将组成的字节码转换成相应的代理class对象;
4.实现MothodIterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色一样)。
报错如下,需要引用asm.jar:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
at fn.proxy.cglib.Meipo.getInstance(Meipo.java:12)
at fn.proxy.cglib.Test.main(Test.java:5)
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 5 more
又来一个报错如下:
class net.sf.cglib.core.DebuggingClassWriter has interface org.objectweb.asm.ClassVisitor as super class
cglib版本为3.0或3.1,org.objectweb.asm版本为3.1.0时,版本冲突,报错java.lang.IncompatibleClassChangeError: class,使用cglib 2.2 可解决此问题,该版本中的DebuggingClassWriter的父类为ClassWriter。
1.创建被代理XiaoMing类
public class XiaoMing {
/**
* 寻找真爱
*/
public void findLove() {
System.out.println("寻找白富美!!!");
}
}
2.创建Meipo类,实现MethodInterceptor接口
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class Meipo implements MethodInterceptor{
public Object getInstance(Class clazz) {
Enhancer enhancer = new Enhancer();
//将父类赋给了谁?
//这里告诉CGLib,生成的子类需要继承的哪个类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//第一步,生成源代码
//第二步,编辑成class文件
//第三步,加载到JVM中,并返回被代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("我是媒婆...");
System.out.println("开始海选...");
//proxy.invoke(obj, args);
/**
* 这里的obj的引用是由CGLib给我们new出来的
* CGLib new出来以后的对象,是被代理对象的子类(继承了我们自己的类)
* OOP在new子类之前,实际上默认先调用了super()方法
* new子类的同时,必须先new父类,这就相当于是间接的持有了我们父类的引用
* 子类重写了父类的所有方法
* 我们改变了子类对象的某些属性,是可以间接操作父类的属性
*/
proxy.invokeSuper(obj, args);
System.out.println("结束海选...");
return null;
}
}
与JDK动态代理相比,cglib可以实现对一般类的代理而无需实现接口。在上例中通过下列步骤来生成目标类Target的代理类:
- 创建Enhancer实例
- 通过setSuperclass方法来设置目标类
- 通过setCallback 方法来设置拦截对象
- create方法生成XiaoMing的代理类,并返回代理类的实例
3.测试Test类代码如下:
import net.sf.cglib.core.DebuggingClassWriter;
public class Test {
public static void main(String[] args) {
//让我们看看通过cglib生成的class文件内容
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/fn");
//JDK的动态代理是通过接口来进行强制转换
//生成以后的代理对象,可以强制转换为接口
//CGLib的动态dialing是通过生成一个被代理对象的子类,然后重写父类的方法
//生成以后的对象,可以强制转换为被代理对象
//子类引用赋值给父类
XiaoMing obj = (XiaoMing) new Meipo().getInstance(XiaoMing.class);
//System.out.println("对象是"+new Meipo().getInstance(XiaoMing.class).getClass());
System.out.println("对象是"+obj.getClass());
obj.findLove();
}
}
运行执行结果如下:
CGLIB debugging enabled, writing to 'F:/fn'
对象是class fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$f3061164
我是媒婆...
开始海选...
寻找白富美!!!
结束海选...
代理类分析
在示例代码中我们通过设置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
的属性值来获取cglib生成的代理类。通过之前分析的命名规则我们可以很容易的在F:/fn下面找到生成的代理类 fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$f3061164
。
使用jd-gui进行反编译(由于版本的问题,此处只能显示部分代码,可以结合javap的反编译结果来进行分析),由于cglib会代理Object中的finalize,equals, toString,hashCode,clone方法,为了清晰的展示代理类我们省略这部分代码,反编译的结果如下:
public class XiaoMing$$EnhancerByCGLIB$$5a444703
extends XiaoMing
implements Factory
{
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$findLove$0$Method;
private static final MethodProxy CGLIB$findLove$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1()
{
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$5a444703");
Class localClass2;
Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = tmp95_92[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
Method[] tmp115_95 = tmp95_92;
CGLIB$equals$2$Method = tmp115_95[1];
CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
Method[] tmp135_115 = tmp115_95;
CGLIB$toString$3$Method = tmp135_115[2];
CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
Method[] tmp155_135 = tmp135_115;
CGLIB$hashCode$4$Method = tmp155_135[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
Method[] tmp175_155 = tmp155_135;
CGLIB$clone$5$Method = tmp175_155[4];
CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
tmp175_155;
Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "findLove", "()V" }, (localClass2 = Class.forName("fn.proxy.cglib.XiaoMing")).getDeclaredMethods());
CGLIB$findLove$0$Method = tmp223_220[0];
CGLIB$findLove$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "findLove", "CGLIB$findLove$0");
tmp223_220;
return;
}
final void CGLIB$findLove$0()
{
super.findLove();
}
public final void findLove()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
if (this.CGLIB$CALLBACK_0 != null) {
return;
}
super.findLove();
}
}
代理类(XiaoMing$$EnhancerByCGLIB$$5a444703
)继承了目标类(XiaoMing),至于代理类实现的Factory接口与本文无关,残忍无视。代理类为每个目标类的方法生成两个方法,例如针对目标类中的每个非private方法,代理类会生成两个方法,以findLove方法为例:一个是@Override的方法,一个是CGLIB$findLove$0
(CGLIB$findLove$0
相当于目标类的findLove方法)。。我们在示例代码中调用目标类的方法obj.findLove()时,实际上调用的是代理类中的findLove()方法。接下来我们着重分析代理类中的findLove方法,看看是怎么实现的代理功能。
当调用代理类的findLove方法时,先判断是否已经存在实现了MethodInterceptor接口的拦截对象,如果没有的话就调用CGLIB$BIND_CALLBACKS
方法来获取拦截对象,CGLIB$BIND_CALLBACKS
的反编译结果如下:
private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
{
5a444703 local5a444703 = (5a444703)paramObject;
if (!local5a444703.CGLIB$BOUND)
{
local5a444703.CGLIB$BOUND = true;
Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
if (tmp23_20 == null)
{
tmp23_20;
CGLIB$STATIC_CALLBACKS;
}
local5a444703.CGLIB$CALLBACK_0 = (tmp31_28 == null ? tmp31_28 : (MethodInterceptor)((Callback[])tmp23_20)[0]);
}
}
CGLIB$BIND_CALLBACKS
先从CGLIB$THREAD_CALLBACKS
中get拦截对象,如果获取不到的话,再从CGLIB$STATIC_CALLBACKS
来获取,如果也没有则认为该方法不需要代理。
那么拦截对象是如何设置到CGLIB$THREAD_CALLBACKS
或者 CGLIB$STATIC_CALLBACKS
中的呢?
在Jdk动态代理中拦截对象是在实例化代理类时由构造函数传入的,在cglib中是调用Enhancer的create方法来生成代理类实例并设置拦截对象的。create的调用轨迹为:
1. Enhancer:AbstractClassGenerator.create
2. Enhancer:firstInstance
3. Enhancer:createUsingReflection
4. Enhancer:setThreadCallbacks
5. Enhancer:setCallbacksHelper
6. Enhancer:CGLIB$SET_THREAD_CALLBACKS
在第6步,调用了代理类的CGLIB$SET_THREAD_CALLBACKS
来完成拦截对象的注入。下面我们看一下CGLIB$SET_THREAD_CALLBACKS
的反编译结果:
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)
{
CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);
}
在CGLIB$SET_THREAD_CALLBACKS
方法中调用了CGLIB$THREAD_CALLBACKS
的set方法来保存拦截对象,在CGLIB$BIND_CALLBACKS
方法中使用了CGLIB$THREAD_CALLBACKS
的get方法来获取拦截对象,并保存到CGLIB$CALLBACK_0
中。
这样,在我们调用代理类的findLove方法时,就可以获取到我们设置的拦截对象,然后通过 tmp4_1.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy)
来实现代理。这里来解释一下intercept方法的参数含义:
@para1 obj :代理对象本身
@para2 method : 被拦截的方法对象
@para3 args:方法调用入参
@para4 proxy:用于调用被拦截方法的方法代理对象
这里会有一个疑问,为什么不直接反射调用代理类生成的(CGLIB$findLove$0
)来间接调用目标类的被拦截方法,而使用proxy的invokeSuper方法呢?这里就涉及到了另外一个点— FastClass 。
Fastclass 机制分析
JDK动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,下面用一个小例子来说明一下,这样比较直观:
public class test10 {
public static void main(String[] args){
Test tt = new Test();
Test2 fc = new Test2();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null);
}
}
class Test{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class Test2{
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
}
上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。代理类(XiaoMing$$EnhancerByCGLIB$$5a444703
)中与生成Fastclass相关的代码如下:
Class localClass1 = Class.forName("fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$5a444703");
localClass2 = Class.forName("fn.proxy.cglib.XiaoMing");
CGLIB$findLove$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "findLove", "CGLIB$findLove$0");
MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$findLove$0
的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:
private static class FastClassInfo
{
FastClass f1; // fn.proxy.cglib.XiaoMing的fastclass
FastClass f2; // XiaoMing$$EnhancerByCGLIB$$5a444703 的fastclass
int i1; //方法findLove在f1中的索引
int i2; //方法CGLIB$findLove$0在f2中的索引
}
MethodProxy 中invokeSuper方法的代码如下:
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
当调用invokeSuper方法时,实际上是调用代理类的CGLIB$findLove$0
方法,CGLIB$findLove$0
直接调用了目标类的findLove方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。