cglib动态代理底层实现分析[ java ]
前言
好久就开始关注cglib动态代理了,但是到今天才算是有点搞明白~cglib动态代理底层用到了asm等字节码操作框架。不懂的可以先百度百度asm是干啥的,在这里也提供一些文章,但是可能比较深入:http://www.jiajiajia.club/search?str=asm
jdk动态代理的原理 http://www.jiajiajia.club/weblog/blog/artical/60
cglib动态代理helloword http://www.jiajiajia.club/blog/artical/134
什么是cglib?
cglib是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
---百度百科。
如何使用cglib创建一个代理类?
需要依赖cglib的jar包,Pom文件如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
helloword代码:
package cglib;
import java.lang.reflect.Method;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* *受理类
* @author Administrator
*
*/
class Target{
public void f(){
System.out.println("Target f()");
}
public void g(){
System.out.println("Target g()");
}
}
/**
* *方法拦截器
* @author Administrator
*
*/
class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
System.out.println("before");//目标方法执行前
proxy.invokeSuper(obj, args);//调用目标类的方法
System.out.println("after");//目标方法执行后
return null;
}
}
/**
* 测试
* @author Administrator
*/
public class MainTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\test\\cglib");
Enhancer eh = new Enhancer();//实例化一个增强器,也就是cglib中的一个class generator
eh.setSuperclass(Target.class);//设置目标类
eh.setCallback(new Interceptor());// 设置拦截对象
Target t = (Target) eh.create();// 生成代理类并返回一个实例
t.f();
t.g();
System.out.println("t对象的父类:"+t.getClass().getSuperclass().getName());
}
}
cglib底层分析
在上述案例中已经把生成的代理类的类文件储存在E:\\test\\cglib中
在文件夹中会发现生成的三个class文件,下面将一一叙述其作用。
先看Target$$EnhancerByCGLIB$$fd8b7134这个类,反编译这个class文件,其中贴出重要代码:
public class Target$$EnhancerByCGLIB$$fd8b7134 extends Target 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$f$0$Method;
private static final MethodProxy CGLIB$f$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$g$1$Method;
private static final MethodProxy CGLIB$g$1$Proxy;
private static final Method CGLIB$finalize$2$Method;
private static final MethodProxy CGLIB$finalize$2$Proxy;
private static final Method CGLIB$equals$3$Method;
private static final MethodProxy CGLIB$equals$3$Proxy;
private static final Method CGLIB$toString$4$Method;
private static final MethodProxy CGLIB$toString$4$Proxy;
private static final Method CGLIB$hashCode$5$Method;
private static final MethodProxy CGLIB$hashCode$5$Proxy;
private static final Method CGLIB$clone$6$Method;
private static final MethodProxy CGLIB$clone$6$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
final Class<?> forName = Class.forName("test.demo.cglib.Target$$EnhancerByCGLIB$$fd8b7134");
final Class<?> forName2;
final Method[] methods = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (forName2 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$2$Method = methods[0];
CGLIB$finalize$2$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()V", "finalize", "CGLIB$finalize$2");
CGLIB$equals$3$Method = methods[1];
CGLIB$equals$3$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");
CGLIB$toString$4$Method = methods[2];
CGLIB$toString$4$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");
CGLIB$hashCode$5$Method = methods[3];
CGLIB$hashCode$5$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()I", "hashCode", "CGLIB$hashCode$5");
CGLIB$clone$6$Method = methods[4];
CGLIB$clone$6$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");
final Class<?> forName3;
final Method[] methods2 = ReflectUtils.findMethods(new String[] { "f", "()V", "g", "()V" }, (forName3 = Class.forName("test.demo.cglib.Target")).getDeclaredMethods());
CGLIB$f$0$Method = methods2[0];
CGLIB$f$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "f", "CGLIB$f$0");
CGLIB$g$1$Method = methods2[1];
CGLIB$g$1$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "g", "CGLIB$g$1");
}
final void CGLIB$f$0() {
super.f();
}
public final void f() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Proxy);
return;
}
super.f();
}
final void CGLIB$g$1() {
super.g();
}
public final void g() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$g$1$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$g$1$Proxy);
return;
}
super.g();
}
}
从代码中可以看出Target$$EnhancerByCGLIB$$fd8b7134这个代理类继承了我们声明的Target类,(注意区别jdk动态代理,jdk动态代理是实现一个接口)。并且以f方法为例,代理类除了生成了重写了Target类的f()方法,还生成了一个CGLIB$f$0()方法,CGLIB$f$0()的方法体中就调用了一下父类的f()方法(先提一下,其实CGLIB$f$0()方法就是我们需要调用目标方法),另外我们在代码中调用f()方法的时候其实调用的就是代理类重写了父类的f()方法。
在Target$$EnhancerByCGLIB$$fd8b7134类的属性中可以看到有个MethodInterceptor属性。在代理对象中MethodInterceptor对象引用的就是我们代码中定义的并且实现MethodInterceptor接口的Interceptor类的对象。那么代理类是怎么引用到的呢?
Enhancer eh = new Enhancer();//实例化一个增强器,也就是cglib中的一个class generator
eh.setSuperclass(Target.class);//设置目标类
eh.setCallback(new Interceptor());// 设置拦截对象
看看main函数的代码就知道了,它是在生成代理对象之前调用setCallback方法传入的。此时代理对象就和MethodInterceptor扯上了关系。
之前说了,代码中调用的f()方法,就是在调用代理对象中的f()方法,在代理对象的f()中可以清楚的看到一行代码:
cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Proxy);
它在干什么?没错,他就在调用我们定义的方法拦截器对象(Interceptor)的intercept方法。也就是说在调用代理对象的f方法的时候,首先就调用了自定义的方法拦截器。在方法拦截其中proxy.invokeSuper(obj, args);这段代码就是在调用目标方法,也即CGLIB$f$0()方法。
从在主函数代码中调用f()方法到调用方法拦截器都很好理解,但是到了调目标方法CGLIB$f$0()就有点绕了。其实它内部是通过Fastclass 机制来实现的。
Fastclass 机制分析
在jkd动态代理中,调用目标方法是通过反射来实现的。而通过反射调用效率是比较低的。所以cglib采用了FastClass的机制来实现对被拦截方法的调用,这也是和jdk代理的一个较大的区别。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。这也是cglib代理效率较jkd代理高的重要原因之一。
举个栗子:
package test.demo.cglib.box;
public class TestFastClass {
public static void main(String[] args){
Target tt = new Target();
FastClass fc = new FastClass();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null);
}
}
/**
* 目标类
*/
class Target{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class FastClass{
/**
* 调用目标方法
*/
public Object invoke(int index, Object obj, Object[] ol){
Target t = (Target) obj;
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;
}
}
上述案例应该很好理解,getIndex()方法中通过不同的方法描述,产生不同的索引值。然后通过索引值到invoke()方法中调取目标方法。
MethodProxy类
MethodProxy是很关键的一个类,也比较看懂。在方法拦截器中就是调用的MethodProxy类对象的invokeSuper方法,通过fastClass机制调用的目标对象。
MethodProxy对象的创建是在创建代理类的时候创建的,对象的信息包含了目标类的类对象、代理类的类对象、方法描述、目标方法的方法名以及代理方法的方法名。其目的就是为了创建FastClass对象。还是以f方法为例:
private static final Method CGLIB$f$0$Method;
private static final MethodProxy CGLIB$f$0$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$f$0$Method = methods2[0];
CGLIB$f$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "f", "CGLIB$f$0");
}
FastClass是什么时候创建的?
走进MethodProxy.invokeSuper()方法。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
在进入init();方法
private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
fastClassInfo就是在init()方法中创建的。并不是和代理类一块生成的。并且是第一次调用时创建,并把它放入了缓存中。看看这个FastClassInfo里面都有什么东西。
接下来反编译Target$$FastClassByCGLIB$$a7520c6d.class,注意为了方便下方好多switch case中的代码被我删了,其实还有equals方法等的索引。
package test.demo.cglib;
import net.sf.cglib.reflect.*;
import net.sf.cglib.core.*;
import java.lang.reflect.*;
public class Target$$FastClassByCGLIB$$a7520c6d extends FastClass{
public Target$$FastClassByCGLIB$$a7520c6d(final Class clazz) {
super(clazz);
}
public int getIndex(final Signature signature) {
final String string = signature.toString();
switch (string.hashCode()) {
case 3078479: {
if (string.equals("f()V")) {
return 0;
}
break;
}
case 3108270: {
if (string.equals("g()V")) {
return 1;
}
break;
}
}
return -1;
}
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
final Target target = (Target)o;
try {
switch (n) {
case 0: {
target.f();
return null;
}
case 1: {
target.g();
return null;
}
}
}
catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
这就是生成后的FastClass的代码。通过getIndex()方法根据目标方法签名获取方法的索引,通过invoke()方法根据方法索引调用目标方法。