CGLib浅析
CGLib浅析
什么是CGLib
CGLIB实现动态代理,并不要求被代理类必须实现接口,底层采用asm字节码生成框架生成代理类字节码(该代理类继承了被代理类)。
所以被代理类一定不能定义为final class
并且对于final
方法不能被代理。
实现需要
//MethodInterceptor接口的intercept方法
/**
*obj 代理对象
*method 委托类方法,被代理对象的方法字节码对象
*arg 方法参数
*MethodProxy 代理方法MethodProxy对象,每个方法都会对应有这样一个对象
*/
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy)
Ehancer enhancer = new Enhancer() //Enhancer为字节码增强器,很方便对类进行扩展
enhancer.setSuperClass(被代理类.class);
enhancer.setCallback(实现MethodInterceptor接口的对象)
enhancer.create()
代码案例
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
UserDaoImpl 用户实现类(RealSubject)
public class UserDaoImpl {
public boolean insert(String name) {
System.out.println("insert name=" + name);
return true;
}
public final boolean insert1(String name) {
System.out.println("final insert name=" + name);
return true;
}
}
CglibProxy CGLIB代理类(Proxy)
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------before----------");
System.out.println("Proxy=" + o.getClass());
System.out.println("method=" + method);
System.out.println("args=" + Arrays.toString(objects));
System.out.println("methodProxy=" + methodProxy);
//执行目标方法对象
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("----------after----------");
return result;
}
}
ProxyFactory 代理工厂
public class ProxyFactory {
private static Enhancer enhancer = new Enhancer();
private static CglibProxy cglibProxy = new CglibProxy();
public static Object getProxy(Class cls) {
enhancer.setSuperclass(cls);
enhancer.setCallback(cglibProxy);
return enhancer.create();
}
public static void main(String[] args) {
UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
userDao.insert("zc");
}
}
CGLib流程
Ehancer enhancer = new Enhancer() //Enhancer为字节码增强器,很方便对类进行扩展
enhancer.setSuperClass(被代理类.class); //为生成的类设置父类
enhancer.setCallback(实现MethodInterceptor接口的对象);
enhancer.create(); //创建代理对象
创建代理对象会经过三步:
1.生成代理类的二进制字节码文件。
2.加载二进制字节码文件到JVM,生成class对象。
3.反射获得实例构造方法,创建代理对象。
接下来,看看反编译出现的Java文件
:
CGLib反编译方法
- 使用以下语句
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\xxxx")
使用
HSDB
进行反编译使用
authas
配合jad
进行反编译
具体使用方法可以自行查找
以insert()
为入口开始:
UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class); //Ehancer,创建代理对象
userDao.insert("zc");
这时候会进入UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2
中的 insert()
public final boolean insert(String string) {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
return object == null ? false : (Boolean)object;
}
return super.insert(string);
}
其实在上述方法中,是因为设置了 enhancer.setCallback(cglibProxy);
,只要不为空,则会执行
Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
this
: 当前代理对象
CGLIB$say$0$Method
: 目标类中的方法
CGLIB$emptyArgs
: 方法参数,这里为空
CGLIB$say$0$Proxy
: 代理类生成的代理方法
这样会去调用 CglibProxy.intercept()
方法
/**
* Object:cglib生成的代理对象
* Method:被代理对象方法
* Object[]:方法入参
* MethodProxy:代理的方法
*/
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------before----------");
//执行目标方法对象
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("----------after----------");
return result;
}
}
这时候进入 methodProxy.invokeSuper(o, objects)
方法
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();
}
}
第一反应,可能不知道是f2
、i2
都是什么,这里扯一下 init()
方法,其中对于FastClass
类,就是反编译出来的对应类:
private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1); // 被代理类FastClass
fci.f2 = helper(ci, ci.c2); // 代理类FastClass
fci.i1 = fci.f1.getIndex(sig1); // 被代理类的方法签名(index)
fci.i2 = fci.f2.getIndex(sig2); // 代理类的方法签名(index)
fastClassInfo = fci;
createInfo = null;
}
}
}
}
private static class FastClassInfo
{
FastClass f1; // 被代理类FastClass
FastClass f2; // 代理类FastClass
int i1; // 被代理类的方法签名(index)
int i2; // 代理类的方法签名(index)
}
fci.f2 = helper(ci, ci.c2); // 代理类FastClass
fci.i2 = fci.f2.getIndex(sig2); // 代理类的方法签名(index)
这时候看到UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5
中
@Override
public int getIndex(Signature signature) {
String string = ((Object)signature).toString();
switch (string.hashCode()) {
//XXXXX 省略
case -747055045: {
if (!string.equals("CGLIB$insert$0(Ljava/lang/String;)Z")) break;
return 16;
}
//XXXXX 省略
return -1;
}
所以 i2
在其中为 16
, 这时候运行下面方法
fci.f2.invoke(fci.i2, obj, args)
即,UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5
中 invoke()
方法
@Override
public Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {
UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2 f32f6ae22 = (UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2)object;
try {
switch (n) {
//XXXXX 省略
case 16: {
return new Boolean(f32f6ae22.CGLIB$insert$0((String)objectArray[0]));
}
//XXXXX 省略
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
可以看到,他进行调用的是 UserDaoImpl$$EnhancerByCGLIB$f32f6ae2
中的 CGLIB$insert$0()
方法
final boolean CGLIB$insert$0(String string) {
return super.insert(string);
}
这里,才是真正调用到了父类(目标类)中对应的方法。至此,整个的调用流程完毕。
流程总结
首先生成代理对象。创建增强类enhancer,设置代理类的父类,设置回调拦截方法,返回创建的代理对象;
调用代理类中的方法。这里调用的代理类中的方法实际上是重写的父类的拦截。重写的方法中会去调用
intercept
方法;调用intercept,方法中会对调用代理方法中的invokeSuper方法。而在
invokeSuper
中维护了一个FastClassInfo
类,其包含四个属性字段,分别为FastClass f1(目标类)
、FastClass f2 (代理类)
、int i1(目标类要执行方法的下标)
、int i2(代理类要执行方法的下标)
; invokeSuper中会调用的为代理类中的对应方法(代理类继承于父类的时候,对于其父类的方法,自己会生成两个方法,一个是重写的方法,一个是代理生成的方法,这里调用的即是代理生成的方法);调用代理类中的代理方法。代理方法中通过
super.xxxx(string)
来实际真正的调用要执行的方法;
思考
这时候,可能心中还有一个疑惑,明明下面两个方法中都有 super.xxxx(string)
, 但是使用的是 invokeSuper()
,而不是 invoke()
看下这两个方法:
final boolean CGLIB$insert$0(String string) {
return super.insert(string);
}
public final boolean insert(String string) {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
return object == null ? false : (Boolean)object;
}
return super.insert(string);
}
- 如果使用
invokeSuper()
:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
//执行被代理类FastClass 的对应 i2 索引的方法
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
就是按照上面讲的步骤,先进行 insert()
方法,经过intercept
,最终可以运行到 CGLIB$insert$0()
,调用到了父类(目标类)中对应的方法。
- 如果使用
invoke()
:
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
//执行代理类FastClass 的对应 i1 索引的方法
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw e;
}
}
i1
的值通过下面方法获取为 1
@Override
public int getIndex(Signature signature) {
String string = ((Object)signature).toString();
switch (string.hashCode()) {
//XXXXX 省略
case -982250262: {
if (!string.equals("insert(Ljava/lang/String;)Z")) break;
return 1;
}
//XXXXX 省略
}
return -1;
}
接着,执行对应方法
@Override
public Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {
UserDaoImpl userDaoImpl = (UserDaoImpl)object;
try {
switch (n) {
//XXXXX 省略
case 1: {
return new Boolean(userDaoImpl.insert((String)objectArray[0]));
}
//XXXXX 省略
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
先进行 insert()
方法,经过intercept
,通过 invoke()
方法,再次进入insert()
方法,继而是一直死循环。
个人博客为:
MoYu's HomePage
CGLib浅析的更多相关文章
- Cglib动态代理浅析
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/18.html 作者:夜月归途 出处:http://www.guitu ...
- 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- JDK动态代理浅析
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/17.html 作者:夜月归途 出处:http://www.guitu ...
- 从底层源码浅析Mybatis的SqlSessionFactory初始化过程
目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...
- 老生常谈系列之Aop--Spring Aop原理浅析
老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...
- 浅析DispatchProxy动态代理AOP
浅析DispatchProxy动态代理AOP(代码源码) 最近学习了一段时间Java,了解到Java实现动态代理AOP主要分为两种方式JDK.CGLIB,我之前使用NET实现AOP切面编程,会用Fil ...
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
随机推荐
- 计算机网络模型和5G知识
目录 一.网络布线及信号传输 1.信号 2.传输介质 2.1双绞线 2.2光纤 2.3常见性问题 三.无线传播介质 四.综合布线系统 五.计算机的数制度以及运算 一.网络布线及信号传输 1.信号 频率 ...
- 2021字节跳动校招秋招算法面试真题解题报告--leetcode206 反转链表,内含7种语言答案
206.反转链表 1.题目描述 反转一个单链表. 示例: 输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1-> ...
- Android Jetpack 架构组件最佳实践之“网抑云”APP
背景 近几年,Android 相关的新技术层出不穷.往往这个技术还没学完,下一个新技术又出来了.很多人都是一脸黑人问号? 不少开发者甚至开始哀嚎:"求求你们别再创造新技术了,我们学不动了!& ...
- linux 源码搭建Kafka集群,100%有效
kafka源码编译安装 准备三台服务器 192.168.xxx.xxx 192.168.xxx.xxx 192.168.xxx.xxx 安装kafka前需先安装JDK和zookeeper如下步骤: J ...
- Qt Designer中自定义控件的使用(提升法与插件法)
准备乱写一点Qt自定义Widget在Designer中的使用.可是又不想重复提升法(promotion)及插件法基本用法,因为Manual中Using Custom Widgets with Qt D ...
- 使用POI把查询到的数据表数据导出到Excel中,一个表一个sheet.最详细!!!
一.需求 我们会遇到开发任务: 经理:小王,你来做一下把数据库里的数据导出到Excel中,一个表是一个sheet,不要一个表一个Excel. 小王:好的,经理.(内心一脸懵逼) 二.前期准备 首先我们 ...
- Debian 11 “bullseye” 安装笔记
作者:gc(at)sysin.org,主页:www.sysin.org Debian 版本:11 代号:bullseye 发布日期:2021.08.14 内核版本:5.10 $ uname -a Li ...
- Android 9.0 默认输入法的设置流程分析
Android 输入法设置文章 Android 9.0 默认输入法的设置流程分析 Android 9.0 添加预置第三方输入法/设置默认输入法(软键盘) 前言 在上一篇文章 Android 9.0 ...
- SQL 练习20
查询各科成绩前三名的记录 select a.sid,a.cid,a.score from sc a left join sc b on a.cid = b.cid and a.score<b.s ...
- 基于taro封装底下浮动弹窗组件
先看效果图: jsx: import Taro, { Component } from '@tarojs/taro' import { View, Image } from '@tarojs/comp ...