原创文章,转载请注明原文章地址,谢谢!
- 最底层的是字节码ByteCode,字节码是Java为了保证“一次编译,到处运行”而产生的一种虚拟的指令格式。
- 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉。
- 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码,只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码。
- 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了。
- 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序。
从一个案列引入Cglib
真实类
public class HelloService {
public HelloService() {
System.out.println("HelloService Constructor...");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService...sayOthers..." + name);
return null;
}
public void sayHello() {
System.out.println("HelloService...sayHello...");
}
}
cglib方法拦截器类
public class MyMethodInterceptor implements MethodInterceptor {
/**
* cglib
*
* @param o cglib生成的代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before advice...");
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("after advice...");
return object;
}
}
测试类
public class Main {
public static void main(String[] args) {
//代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\workspace\\demo");
//通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
//设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
//设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
//创建代理对象
HelloService proxy = (HelloService) enhancer.create();
//通过代理对象调用目标方法
proxy.sayHello();
}
}
测试结果
HelloService Constructor...
before advice...
HelloService...sayHello...
after advice...
从案例代码可以了解到,如果你了解过jdk的动态代理,那么与之比较,jdk动态代理需要实现代理类的接口,编写调用处理器,且要实现InvocationHandler。而cglib采用的是继承的方式,最终生成的代理类,是继承了真实类。编写自定义的方法拦截器,都要实现MethodInterceptor,这是使用cglib代理必须要实现的接口,然后通过其intercept方法实现具体的逻辑。而代理类是怎样和真实类产生联系的呢?看上述的测试代码,通过Enhancer对象创建代理类,并设置了其父类就是真实类,这样生成的代理类就和真实类有了继承关系。那么这个Enhancer是什么呢?它是一个字节码增强器,用来为无接口的类创建代理的,这里先不过多介绍。
在上述运行测试的过程中,因为在其上面设置类保存字节码到磁盘,所以在运行结束后,磁盘怒路下多了几个class文件,这些便是在运行过程中生成的相关的代理类。不同于jdk的生成的代理类,这里cglib生成很多代理类。这其实是因为cglib在运行的过程中,为真实类,以及这个过程中用到的FastClass都生成了代理类。这些具体是什么东东,我们后面再说。一个小小的案例,让我们知道了如何使用cglib,以及最后的结果。但是如果你是一个愿意刨根问底的,那么一定会有一些疑问。cglib到底是怎样完成这样的代理的?是如果实现方法拦截的?所以接下来呢,我们还是要带着问题,一起深入了解一下。
Cglib原理分析
我们先从生成的字节码入手,看看cglib如何利用它们来完成代理的。HelloService$$EnhancerByCGLIB$$750c1a60.class。前面说了,cglib是采用继承的方式,所以结果也不难看出,生成的字节码类继承了真实类,这样是可以重写其父类的方法,并且还可以添加一些额外的逻辑。
public class HelloService$$EnhancerByCGLIB$$750c1a60 extends HelloService implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
//真实对象的方法
private static final Method CGLIB$sayHello$0$Method;
//代理对象的代理方法(都与真实对象的方法一一对应)
private static final MethodProxy CGLIB$sayHello$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
//静态代码块,会优先加载
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
//利用Class.forName创建对象,后面的全限定名就是代理对象
Class var0 = Class.forName("com.alibaba.proxy.cglib.HelloService$$EnhancerByCGLIB$$750c1a60");
//这里的var1会在下面进行创建,这里只是声明
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
//上面的var1声明,这里进行创建对象并赋值。同样是通过Class.forName创建,后面是真实对象的全限定名
CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.alibaba.proxy.cglib.HelloService")).getDeclaredMethods())[0];
//创建一个MethodProxy对象,将代理类、真实类、代理方法、真实方法都作为入参
CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
}
CGLIB$sayHello$0()方法,是代理方法,methodProxy.invokeSuper会调用。下面的sayHello是真实方法,methodProxy.invoke会调用。这里解释了为什么在拦截器中调用methodProxy.invoke会死循环,因为下面的代码中一直在调用intercept。
final void CGLIB$sayHello$0() {
super.sayHello();
}
public final void sayHello() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
//调用intercept方法,也就是在自定义的Intercepter类中实现的方法。
var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
} else {
super.sayHello();
}
}
一直到上面,简单说一下流程:代理对象调用sayHello,然后调用拦截器intercept,然后执行拦截器中的前面代码,然后执行invokeSuper,会执行CGLIB$sayHello$0方法,然后调用父类sayHello,然后执行拦截器后面代码。
那么从invokeSuper方法中为什么就能调用到CGLIB$sayHello$0()方法呢?
- MethodProxy.create:在代理类中create一个MethodProxy,将被代理对象与方法传入Signature1,将代理对象与方法传入Signature2,将代理类对象与被代理类对象传入MethodProxy.CreateInfo,Signature1与MethodProxy.CreateInfo可以认为是实体类存储信息的。
- 调用methodProxy.invokeSuper了,调用invokeSuper时又会首先调用init方法。
- init方法就是给在create方法中没有赋值的fastclassInfo赋值,将createInfo与Signature1与Signature2中的信息取出,转成fastclass,如果fastclass在缓存中有就从缓存中取,没有的话就生成新的fastclass。
- 通过Signature1与Signature2获得方法的索引,存入i1与i2。
- 通过fci.f2.invoke(fci.i2, obj, args),fastclass反射调用代理对象的代理方法的索引,直接定位到代理方法。
- 代理方法中调用super.sayHello()
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private CreateInfo createInfo;
private final Object initLock = new Object();
private volatile FastClassInfo fastClassInfo;
/**
* For internal use by {@link Enhancer} only; see the {@link org.springframework.cglib.reflect.FastMethod} class
* for similar functionality.
*/
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
//fastCLass.invoke(代理方法索引,代理对象,方法参数)
return fci.f2.invoke(fci.i2, obj, args);
}
catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
MethodProxy调用流程
- 首先main方法中调用getInstance调用Enhancer创建代理类class文件,并将代理类引用返回。
- 然后调用代理对象的sayHello()
- 然后通过反编译代理类,查看sayHello方法,发现在sayHello中会调用拦截器方法。
- 然后在拦截器方法中会通过MethodProxy调用invokeSuper方法。
FastClass机制
Cglib动态代理执行代理方法效率之所以比JDK高是因为Cglib采用了FastClass机制,它为代理类和被代理类各生成了一个class,这个class会为代理类与被代理类的方法分类index。这个index作为方法参数,FastClass可以直接定位到要调用的方法进行调用,这样省去了反射调用,所以效率比JDK动态代理快。FastClass不是与代理类一起生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放入缓存。
public class HelloService$$FastClassByCGLIB$$8656ab6f extends FastClass {
public HelloService$$FastClassByCGLIB$$8656ab6f(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1488716497:
if (var10000.equals("sayOthers(Ljava/lang/String;)Ljava/lang/String;")) {
return 1;
}
break;
}
return -1;
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
HelloService var10000 = (HelloService)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.sayHello();
return null;
case 1:
return var10000.sayOthers((String)var3[0]);
case 2:
return new Boolean(var10000.equals(var3[0]));
case 3:
return var10000.toString();
case 4:
return new Integer(var10000.hashCode());
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
}
}
因为生成的FastClass代理类较长,这里只截取其中一部分,只为了说明问题。这里面包含了getIndex方法和invoke方法。getIndex方法会为代理类和真实类的方法分类index,这样每个index会对应到具体方法,那么在上面那个invokeSuper那里,实际上就是用fastClass对象调用的invoke方法,传入的参数就包含这个index,那么在invoke方法中,就直接根据index定位到具体方法执行了。
jdk动态代理和cglib动态代理的区别
1、jdk动态代理是实现了被代理对象的接口,cglib是继承了被代理对象。
2、jdk和cglib都是在运行期生成字节码,jdk是直接写class字节码,cglib使用ASM框架写class字节码,cglib代理实现更复杂,生成代理类比jdk效率低。
3、jdk调用代理方法,是通过反射机制调用,cglib是通过FastClass机制直接调用方法,cglib执行效率更高。
博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!
网友评论