【深度思考】聊聊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 ...
随机推荐
- oracle 白名单作用及配置教程
出于提高数据安全性等目地,我们可能想要对oracle的访问进行限制,允许一些IP连接数据库或拒绝一些IP访问数据库. 当然使用iptables也能达到限制的目地,但是从监听端口变更限制仍可生效.只针对 ...
- animation 和 transition 的区别
Transition 提供了从一种状态过渡到另一种状态的改变. Animation 则可以从不同关键帧(@keyframes)上设置多个过渡点. Transition 关注的是元素指定css属性的变化 ...
- Zookeeper分布式服务
Zookeeper(CP) 以集群的方式[leader和follower]为分布式应用提供协调服务.负责存储和管理大家都关系的数据,接受观察者注册.消息分发等服务 特点: 只要有半数以上的节点存活就能 ...
- 实验二 c语言中的表达式及输入输出函数编程应用
1. 格式符%04d的作用是:在左边填充数字0,输出变量的所有数字且左对齐 #include <stdio.h>int main() { int num; scanf("% ...
- NuGet国内镜像
NuGet国内镜像 https://nuget.cdn.azure.cn/v3/index.json
- Java定时器Timer和TimerTask
方式一:设定指定任务task在指定时间time执行 schedule(TimerTask task, Date date) public static void main(String[] arg ...
- day49-数据类型、约束条件
数据类型: 1.整型--默认情况下都是带有符号的, id int(8)-- 如果数字没有超过9位,默认用0填充,如果数字超出8位,有几位存几位 总结:针对整型字段,括号内无需指定宽度,因为它默认的宽度 ...
- python之tk学习,闲鱼搜索-小记
(如想转载,请联系博主或贴上本博地址) 编程,逻辑,总是让人如痴如醉. 下面进入正题. 火热的天气配上火热的python,python的入门友好性让门外汉们都看到了希望.当然自己写的程序如果没有GUI ...
- 查看服务器cpu 核心数
cpu相关信息所在文件是 /proc/cpuinfo 物理cpu数 # grep "physical id" /proc/cpuinfo | sort | uniq | wc -l ...
- rsync+inotify组合实现实时同步
首先准备两台服务器(centos7) A:192.168.75.160 B:192.168.75.161 A机器当做客户端,B机器当做服务端 rsync 安装 客户端服务器端都要安装rsync ,但是 ...