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);
}

这里,才是真正调用到了父类(目标类)中对应的方法。至此,整个的调用流程完毕。

流程总结

  1. 首先生成代理对象。创建增强类enhancer,设置代理类的父类,设置回调拦截方法,返回创建的代理对象;

  2. 调用代理类中的方法。这里调用的代理类中的方法实际上是重写的父类的拦截。重写的方法中会去调用intercept方法;

  3. 调用intercept,方法中会对调用代理方法中的invokeSuper方法。而在 invokeSuper 中维护了一个 FastClassInfo类,其包含四个属性字段,分别为FastClass f1(目标类)、FastClass f2 (代理类)、int i1(目标类要执行方法的下标)、int i2(代理类要执行方法的下标); invokeSuper中会调用的为代理类中的对应方法(代理类继承于父类的时候,对于其父类的方法,自己会生成两个方法,一个是重写的方法,一个是代理生成的方法,这里调用的即是代理生成的方法);

  4. 调用代理类中的代理方法。代理方法中通过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浅析的更多相关文章

  1. Cglib动态代理浅析

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/18.html 作者:夜月归途 出处:http://www.guitu ...

  2. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  3. jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  4. JDK动态代理浅析

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/17.html 作者:夜月归途 出处:http://www.guitu ...

  5. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  6. 老生常谈系列之Aop--Spring Aop原理浅析

    老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...

  7. 浅析DispatchProxy动态代理AOP

    浅析DispatchProxy动态代理AOP(代码源码) 最近学习了一段时间Java,了解到Java实现动态代理AOP主要分为两种方式JDK.CGLIB,我之前使用NET实现AOP切面编程,会用Fil ...

  8. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  9. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

随机推荐

  1. XSS靶机

    第一关 localhost:8083/xss/level1.php?name=test<script>alert(1)</script> 第二关 源码 文本框输入js代码,查看 ...

  2. xss.haozi靶机

    X00 <script>alert(1)</script> X01 </textarea><script>alert(1)</script> ...

  3. 2020年度钻石C++C学习笔记(1)《博学谷》

    1.C语言概述 1.1 什么是C语言 一提到语言这个词语,自然会想到的是像英语.汉语等这样的自然语言,因为它是人和人交换信息不可缺少的工具. 而今天计算机遍布了我们生活的每一个角落,除了人和人的相互交 ...

  4. Go连接到Linux服务器进行操作-执行shell命令&&上传文件

    Go连接到Linux服务器进行操作 使用密码连接Linux服务器 package main import ( "fmt" "golang.org/x/crypto/ssh ...

  5. Java基础技术-Java其他主题【面试】

    Java基础技术-Java其他主题[面试] Java基础技术IO与队列 Java BIO.NIO.AIO Java 中 BIO.NIO.AIO 的区别是什么? 含义不同: BIO(Blocking I ...

  6. Kurento实战之二:快速部署和体验

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. pikachu PHP反序列化 XXE SSRF

    PHP反序列化在理解这个漏洞前,你需要先搞清楚php中serialize(),unserialize()这两个函数. 另外这个漏洞一般是在代码审计的时候发现的,在扫描或者黑盒测试的时候很难发现.1.序 ...

  8. 搭建riscv环境

    1.第一步 clone 需要的源文件(sudo apt install git   如果你没有git)  以下都是给予ubuntu18.04 $ git clone --recursive https ...

  9. ☕【Java技术指南】「编译器专题」重塑认识Java编译器的执行过程(常量优化机制)!

    问题概括 静态常量可以再编译器确定字面量,但常量并不一定在编译期就确定了, 也可以在运行时确定,所以Java针对某些情况制定了常量优化机制. 常量优化机制 给一个变量赋值,如果等于号的右边是常量的表达 ...

  10. Java-线程池专题 (美团)

    实现多线程的三种方式,继承Thread,实现Runnable 和 实现 Executor接口 ,具体参考:Java 多线程 三种实现方式 去美团,问到了什么是线程池,如何使用,为什么要用,以下做个总结 ...