来一段简单的cglib代码

 public class SampleClass {
public void test(){
System.out.println("hello world");
} public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
result = proxy.invoke(obj, args);
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}

代码中使用 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes")设置环境变量,此设置可以打印生成的字节码文件。

受影响的方法为:org.springframework.cglib.core.DebuggingClassWriter#toByteArray这里使用了spring的cglib包:spring的cglib包仅仅修改了cglib的类路径,实现完全相同

运行过程中,cglib会生成3个class文件,第一个class文件的生成触发点在测试类第20行,对SampleClass进行增强,生成的关键代码如下:

 public class SampleClass$$EnhancerByCGLIB$$8ed28f extends SampleClass implements Factory {
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$test$0$Method;
private static final MethodProxy CGLIB$test$0$Proxy;
private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK1() {
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.example.demo.proxy.SampleClass$$EnhancerByCGLIB$$8ed28f");
Class var1;
CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.example.demo.proxy.SampleClass")).getDeclaredMethods())[0];
CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
} final void CGLIB$test$0() {
super.test();
} public final void test() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
} if(var10000 != null) {
var10000.intercept(this, CGLIB$test$0$Method, CGLIB$emptyArgs, CGLIB$test$0$Proxy);
} else {
super.test();
}
} private static final void CGLIB$BIND_CALLBACKS(Object var0) {
SampleClass$$EnhancerByCGLIB$$8ed28f var1 = (SampleClass$$EnhancerByCGLIB$$8ed28f)var0;
if(!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if(var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if(CGLIB$STATIC_CALLBACKS == null) {
return;
}
} var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
} } public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
} static {
CGLIB$STATICHOOK1();
}
}

测试代码第10行:enhancer.setCallback(**)将拦截器设置到增强代码中。

执行test()方法,实际上调用的是增强代码的20行test()方法,增强的方法会调用注册的拦截器。方法参数为:

Object obj 增强的SampleClass$$EnhancerByCGLIB$$8ed28f实例
Method method 原生test方法
Object[] args 此处没有参数,为空
MethodProxy proxy 生成的methodProxy

接下来我们看下methodProxy的生成:增强类静态块中调用了CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");

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

只是记录了一些类信息。

测试代码执行:proxy.invokeSuper(obj, args);

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

展开init方法

 private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}

12                     fci.f1 = helper(ci, ci.c1);
13 fci.f2 = helper(ci, ci.c2); 这2行分别生成的2个fastClass类,通过类的signature快速定位方法
12                     fci.f1 = SampleClass$$FastClassByCGLIB$$4f454a14 
 public class SampleClass$$FastClassByCGLIB$$4f454a14 extends FastClass {

     public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1422510685:
if(var10000.equals("test()V")) {
return 1;
}
break; return -1;
} public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
SampleClass var10000 = (SampleClass)var2;
int var10001 = var1; try {
switch(var10001) {
case 1:
var10000.test();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
} throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
13                     fci.f2 = SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b
 public class SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b extends FastClass {

     public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1659809612:
if(var10000.equals("CGLIB$test$0()V")) {
return 16;
}
break;
case -1422510685:
if(var10000.equals("test()V")) {
return 7;
}
break;
return -1;
} public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
8ed28f var10000 = (8ed28f)var2;
int var10001 = var1; try {
switch(var10001) {
case 7:
var10000.test();
return null;
case 16:
var10000.CGLIB$test$0();
return null;
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
} throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}

invokeSuper调用fci.f2.invoke(fci.i2, obj, args),使用的是第三个生成类,方法签名是:CGLIB$test$0

通过方法签名的hashcode映射后得到索引为16

 6         case -1659809612:
7 if(var10000.equals("CGLIB$test$0()V")) {
8 return 16;
9 }
10 break;

invoke调用的时候

28             case 16:
29 var10000.CGLIB$test$0();
30 return null;
走的这段逻辑。对比增强类可以得知CGLIB$test$0()是对原生方法的存根,执行的是最原始的逻辑。 invoke调用

     public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
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;
}
}

fci.f1.invoke(fci.i1, obj, args)使用的是第二个生成类,方法签名是:test

通过方法签名的hashcode映射后得到索引为1

 6         case -1422510685:
7 if(var10000.equals("test()V")) {
8 return 1;
9 }
10 break;

invoke调用的时候



21             case 1:
22 var10000.test();
23 return null;
24 }
走的这段逻辑。对比增强类可以得知test()是增强方法,注册了拦截调用,所以才会出现循环调用,最终导致栈深操作过大范围,出现内存溢出。

 
 

深入字节码理解invokeSuper无限循环的原因的更多相关文章

  1. 学以致用,通过字节码理解:Java的内部类与外部类之私有域访问

    目录: 内部类的定义及用处 打开字节码理解内部类 一.内部类的定义及用处 内部类(inner class)是定义在另一个类中的类.使用内部类,我们可以: 访问该类定义所在的作用域中的数据,包括私有的数 ...

  2. Java精通并发-透过字节码理解synchronized关键字

    在上一次https://www.cnblogs.com/webor2006/p/11428408.html中对于synchronized关键字的作用做了一个实例详解,下面再来看一下这个程序: 请问下, ...

  3. java字节码理解-入门

    前记:作为一名JAVA Developer,每次打开Eclipse,查找一个没有源码的类时,都会看到一个这样的画面: 大意是:这个jar文件,没有附带源码.紧接着后面的就看不懂了,很好奇下面的一部分是 ...

  4. 理解 vue-router的beforeEach无限循环的问题

    在理解beforeEach无限循环之前,我们先来看一下beforeEach相关的知识点,该篇文章的项目是基于 express+vue+mongodb+session实现注册登录 这篇文章项目基础之上进 ...

  5. JVM学习——字节码(学习过程)

    JVM--字节码 为什么要学字节码 字节码文件,有什么用? JVM虚拟机的特点:一处编译,多处运行. 多处运行,靠的是.class 字节码文件. JVM本身,并不是跨平台的.Java之所以跨平台,是因 ...

  6. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  7. 深入理解JVM—字节码执行引擎

    原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...

  8. 《深入理解Java虚拟机》学习笔记之字节码执行引擎

    Java虚拟机的执行引擎不管是解释执行还是编译执行,根据概念模型都具有统一的外观:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 栈帧(Stack Frame) ...

  9. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

随机推荐

  1. List<T>中,Remove和RemoveAt区别

    Remove删除的是匹配的第一项.比如你的list里面有2个相同的项.那么就删除第一个.后面的不删除,找不到元素和删除失败都返回falseRemoveAt是删除索引下的项

  2. kbmmw ORM 对象定义语法简析

    使用kbmmw 的ORM 一定先要了解ORM 的对象定义语法. 下面简单说一下 // kbmMW_Table - Define a table. 定义一个表 // Must be used on cl ...

  3. MyBatis中实现多表查询

    如果查询的数据量大,推荐使用N+1次查询.数据量少使用联合查询... 一. 1.Mybatis是实现多表查询方式 1.1  业务装配:对两个表编写单表查询语句,在业务(Service)把查询的两表结果 ...

  4. 680. Valid Palindrome II

    static int wing=[]() { std::ios::sync_with_stdio(false); cin.tie(NULL); ; }(); class Solution { publ ...

  5. hibernate添加数据报错:Could not execute JDBC batch update

    报错如下图所示: 报错原因:在配置文件或注解里设置了字段关联,但数据却没有关联. 解决方法:我的错误是向一个多对多的关联表里插入数据,由于表中一个字段的数据是从另一张表里get到的,通过调试发现,从以 ...

  6. C++ MFC棋牌类小游戏day1

    好用没用过C++做一个完整一点的东西了,今天开始希望靠我这点微薄的技术来完成这个小游戏. 我现在的水平应该算是菜鸟中的战斗鸡了,所以又很多东西在设计和技术方面肯定会有很大的缺陷,我做这个小游戏的目的单 ...

  7. highChart图表

    Highcharts 是一个用纯JavaScript编写的一个图表库, 能够很简单便捷的在web网站或是web应用程序添加有交互性的图表,并且免费提供给个人学习.个人网站和非商业用途使用.HighCh ...

  8. WordPaster-dedecms5.7整合教程

    1.1. 与dedecms5.7整合 本教程中提到的插件文件可在官网的php-ckeditor3x示例中找到. 示例:http://www.ncmem.com/download/WordPaster2 ...

  9. js基础学习笔记(一)

    * 在js编写过程中,尽量保持统一使用单引号 'XXXX': * 所有变量都要声明 var,避免全局函数调用的冲突: 1.1    输出内容 docment.write(‘aileLi’); 改变某I ...

  10. C#-VS发布网站-摘

    在vs生成发布文件 现在已经有了网站,可以发布了.可以将网站发布到您可以使用 Visual Studio 支持的任何连接协议访问的任何位置.复制网站有下面几种方式可选: 复制到本地计算机上的文件夹. ...