【深度思考】聊聊CGLIB动态代理原理
1. 简介
CGLIB的全称是:Code Generation Library。
CGLIB是一个强大的、高性能、高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口,
底层使用的是字节码处理框架ASM。
Github地址:https://github.com/cglib/cglib。
CGLIB的Maven坐标如下所示:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2. 示例
首先,新增一个类:
public class Coder {
public void work() {
System.out.println("认真写bug……");
}
}
然后,自定义一个方法拦截器,实现net.sf.cglib.proxy.MethodInterceptor
接口并重写intercept
方法:
public class AttendanceMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("上班打卡……");
Object result = proxy.invokeSuper(obj, args);
System.out.println("下班打卡……");
return result;
}
}
重点看下Object result = proxy.invokeSuper(obj, args);
,该行代码最终会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。
然后,新建个测试类,看下CGLIB动态代理如何使用:
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Coder.class);
enhancer.setCallback(new AttendanceMethodInterceptor());
// 创建代理对象
Object object = enhancer.create();
Coder coder = (Coder) object;
coder.work();
}
}
运行以上代码,效果如下图所示:
从运行结果可以看出,在目标方法的前后,执行了自定义的操作。
3. 原理
看下上面的测试类代码,首先是创建了一个net.sf.cglib.proxy.Enhancer
对象,然后调用了setSuperclass()
方法
将enhancer对象的父类设置为Coder类:
紧接着调用了setCallback()
方法将enhancer对象的方法拦截器设置为自定义的AttendanceMethodInterceptor:
然后是调用enhancer对象的create()
方法来生成一个代理对象。
先打印下,简单看下这个代理类的信息:
图中的com.zwwhnly.mybatisplusdemo.cglibproxy.Coder$$EnhancerByCGLIB$$8e91f654
就是CGLIB生成的代理类的名称。
那么这个代理类具体是什么样子呢?
在上面的测试类代码中(Object object = enhancer.create();
代码之前)添加以下一行代码:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");
然后再次运行,会看到项目根目录下生成了一个cglib文件夹,自动生成的代理类就包含在其中:
可以看到一共生成了5个类,这里重点关注下红色标记的3个类。
先看下Coder$$EnhancerByCGLIB$$8e91f654.class
,这个类就是自动生成的代理类:
可以看出Coder$$EnhancerByCGLIB$$8e91f654.class
继承了Coder类(也就是说自动生成的代理类其实是被代理类的一个子类),
并且重写了Coder类的work()方法,重写后的work()方法会调用自定义的方法拦截器AttendanceMethodInterceptor里的intercept()
方法。
然后看下Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,从名称上可以看出这个类的前半段和上面的类的名称
是一样的,后半段拼接上了$$FastClassByCGLIB$$4e5eb5aa
,从功能上说,这个类是上面的代理类的索引类,重点关注下里面的
getIndex()
方法和invoke()
方法:
最后看下Coder$$FastClassByCGLIB$$398819d0
,这个类是被代理类Coder的索引类,重点也是关注下里面的
getIndex()
方法和invoke()
方法:
知道了这3个类的作用后,再一步一步看下示例代码中coder.work();
的调用过程,因为coder是生成的代理类的实例,所以
coder.work();
首先调用的是Coder$$EnhancerByCGLIB$$8e91f654
的work()方法:
这里的var10000是自定义的方法拦截器AttendanceMethodInterceptor,所以执行的是红色截图里的intercept()
方法,也就是:
然后看下invokeSuper()
方法:
首先执行的是init()
方法,在该方法内部对fastClassInfo字段进行了赋值:
从上图可以看出,fci.f1是自动生成的Coder类的索引类Coder$$FastClassByCGLIB$$398819d0
,所以fci.i1 = fci.f1.getIndex(sig1);
其实执行的是的Coder$$FastClassByCGLIB$$398819d0
的getIndex()
方法:
fci.f2是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,
所以fci.i2 = fci.f2.getIndex(sig2);
其实执行的是的Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
的
getIndex()
方法:
看完init()
方法后再回到invokeSuper()
方法:
上图中的FastClassInfo fci = fastClassInfo;
使用到的字段fastClassInfo在init()
方法内部已经赋过值,
fci.f2其实是自动生成的代理类的索引类Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,
fci.i2值是1,
所以fci.f2.invoke(fci.i2, obj, args);
实际执行的是:
这里的var10000其实是自动生成的代理类Coder$$EnhancerByCGLIB$$8e91f654
的实例,所以接着调用的是
代理类Coder$$EnhancerByCGLIB$$8e91f654
的CGLIB$work$0()
方法:
这里的super指的是Coder类,所以super.work();
实际执行的是Coder类的work()方法:
综上所述,coder.work();
的调用顺序依次是:
代理类--->自定义方法拦截器--->代理类索引类getIndex()方法-->代理类索引类invoke()方法--->代理类--->被代理类。
4. JDK动态代理与CGLIB动态代理区别(面试常问)
关于JDK动态代理,可以查看上一篇博客:【深度思考】聊聊JDK动态代理原理。
了解了JDK动态代理和CGLIB动态代理的原理后,现在来比较下两者的区别,这也是面试时几乎必问的一道面试题。
使用JDK动态代理,被代理类必须要实现接口,使用CGLIB动态代理,被代理类可以不实现接口
原因分析:
JDK动态代理生成的代理类继承了
java.lang.reflect.Proxy
,因为Java是单继承的,如果不通过实现接口的形式,无法对类进行扩展。
CGLIB动态代理生成的代理类实际上是被代理类的子类,所以被代理类可以不实现接口。
自动生成类的数量不同
JDK动态代理只会生成1个代理类,一般情况下名称为:
com.sun.proxy.$Proxy0
。CGLIB动态代理会生成好几个类,核心的3个分别是:
1)代理类:被代理类的子类,名称格式为
Coder$$EnhancerByCGLIB$$8e91f654
,包名和被代理类包名一致。2)代理类的索引类:名称格式为
Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,包名和被代理类包名一致。
3)被代理类的索引类:名称格式为
Coder$$FastClassByCGLIB$$398819d0
,包名和被代理类包名一致。生成代理类技术不同
JDK动态代理使用JDK自带的ProxyGenerator类生成字节码文件。
CGLIB动态代理使用ASM框架生成字节码文件。
调用方式不同
JDK动态代理:代理类--->InvocationHandler.invoke()--->被代理类方法(用到了反射)。
CGLIB动态代理:代理类--->MethodInterceptor.intercept()--->代理类索引类getIndex()--->
代理类索引类invoke()--->代理类--->被代理类。(直接调用)
文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!
【深度思考】聊聊CGLIB动态代理原理的更多相关文章
- JDK和CGLIB动态代理原理区别
JDK和CGLIB动态代理原理区别 https://blog.csdn.net/yhl_jxy/article/details/80635012 2018年06月09日 18:34:17 阅读数:65 ...
- CGLib动态代理原理及实现
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了.CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采 ...
- cglib源码分析(四):cglib 动态代理原理分析
本文分下面三个部分来分析cglib动态代理的原理. cglib 动态代理示例 代理类分析 Fastclass 机制分析 一.cglib 动态代理示例 public class Target{ publ ...
- java cglib动态代理原理及样例
cglib动态代理: http://blog.csdn.net/xiaohai0504/article/details/6832990 一.原理 代理为控制要访问的目标对象提供了一种途径.当访问 ...
- Java Proxy和CGLIB动态代理原理
动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...
- JDK和CGLIB动态代理原理
1.JDK动态代理利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类, 在调用具体方法前调用InvokeHandler来处理. 2.CGLiB动态代 ...
- Spring源码剖析5:JDK和cglib动态代理原理详解
AOP的基础是Java动态代理,了解和使用两种动态代理能让我们更好地理解 AOP,在讲解AOP之前,让我们先来看看Java动态代理的使用方式以及底层实现原理. 转自https://www.jiansh ...
- 动态代理:JDK原生动态代理(Java Proxy)和CGLIB动态代理原理+附静态态代理
本文只是对原文的梳理总结,以及自行理解.自己总结的比较简单,而且不深入,不如直接看原文.不过自己梳理一遍更有助于理解. 详细可参考原文:http://www.cnblogs.com/Carpenter ...
- 有点深度的聊聊JDK动态代理
在接触SpringAOP的时候,大家一定会被这神奇的功能所折服,想知道其中的奥秘,底层到底是如何实现的.于是,大家会通过搜索引擎,知道了一个陌生的名词:动态代理,慢慢的又知道了动态代理有多种实现方式, ...
- 【java高级编程】JDK和CGLIB动态代理区别
转载:https://blog.csdn.net/yhl_jxy/article/details/80635012 前言 JDK动态代理实现原理(jdk8):https://blog.csdn.net ...
随机推荐
- FCC 中级算法题 Binary Agents
Binary Agents 传入二进制字符串,翻译成英语句子并返回. 二进制字符串是以空格分隔的. String.charCodeAt() String.fromCharCode() 思路: (1)把 ...
- 解决Tomcat 启动,http://localhost:8080无法访问 问题
注意:1:tomact能正常启动 2 : 通过startup启动后可以正常访问8080界面: 3: 通过eclipse启动后无法正常访问8080界面: Tomcat能在eclipse里面能正常启动,但 ...
- C语言中分治的思想-位运算
(1) 有101个整数,其中有50个数出现了两次,1个数出现了一次, 找出出现了一次的那个数. 位运算的思想:任何数和零异或得到自己,任何数自己异或得到0. eg1.{2,1,5,1,2,2} num ...
- 安装DevExpress VCL,使用时报错 某单元文件找不到的解决办法
1.新建一个工程做为测试 2.点击projecct-->options 3. 4. 5.在上4图上的红框内加入packages文件 dxCoreRS27;dxGDIPlusRS27;dxComn ...
- [Unity移动端]Mono与IL2Cpp
参考链接: https://blog.csdn.net/linxinfa/article/details/87358809 https://blog.csdn.net/Aison_/article/d ...
- linux修改用户密码期限
1. https://www.cnblogs.com/wwwcf1982603555/p/15474557.html 设置密码复杂度: http://events.jianshu.io/p/533d3 ...
- redis 访问 database
edis的数据库个数是可以配置的,默认为16个,见redis.windows.conf/redis.conf的databases 16.对应数据库的索引值为0 - (databases -1),即16 ...
- 内网jenkins跨版本升级
概要: 原来使用的jenkins版本为1.6,现在需要升级为最新版2.3.6 由于在内网,不能使用jenkins自带的在线升级工具 升级思路: 由于版本跨度太大,直接copy jenkins目录,启动 ...
- OD机试题-2022.4
import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Scanne ...
- ESP32 优化 IRAM 内存方法整理 ---ESP32
有以下三种方便的方法来优化 IRAM 内存: 启用 menuconfig -> Compiler option -> Optimization Level -> Optimize f ...