使用cglib中BeanCopier遇到的问题

问题描述

spring boot项目,idea开发环境使用BeanCopier的copy方法没有任何问题
使用maven打包成jar包后,用java -jar 命令执行,提示java.lang.VerifyError: Bad type on operand stack错误
新建TestMain类,单独测试copy方法同样提示错误
详细错误如下

Exception in thread "main" java.lang.IllegalStateException: Unable to load cache item
        at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:79)
        at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
        at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)
        at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
        at net.sf.cglib.beans.BeanCopier$Generator.create(BeanCopier.java:95)
        at net.sf.cglib.beans.BeanCopier.create(BeanCopier.java:51)
        at TestMain.<init>(TestMain.java:8)
        at TestMain.main(TestMain.java:13)
Caused by: java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
TestMain$UserDO$$BeanCopierByCGLIB$$eb78a326.copy(Ljava/lang/Object;Ljava/lang/Object;Lnet/sf/cglib/core/Converter;)V @12: invokevirtual
  Reason:
    Type 'java/lang/Object' (current frame, stack[3]) is not assignable to 'java/lang/Long'
  Current Frame:
    bci: @12
    flags: { }
    locals: { 'TestMain$UserDO$$BeanCopierByCGLIB$$eb78a326', 'java/lang/Object', 'java/lang/Object', 'net/sf/cglib/core/Converter' }
    stack: { 'TestMain$UserDO', 'TestMain$UserVO', 'TestMain$UserDO', 'java/lang/Object' }
  Bytecode:
    0x0000000: 2cc0 000d 2bc0 000f 5cb6 0015 b600 195c
    0x0000010: b600 1db6 0021 b1
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:467)
        at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
        at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)
        at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)
        at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
        at java.util.concurrent.FutureTask.run(Unknown Source)
        at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
        ... 7 more

执行环境

-jdk 1.8.0_171
-cglib 3.3.0
-asm 7.2
-lombok 1.18.10
-Idea 2019.3
TestMain中示例代码如下

import lombok.Data;
import lombok.Getter;
import net.sf.cglib.beans.BeanCopier;

public class TestMain {

    @Getter
    private final BeanCopier dto2do = BeanCopier.create(UserVO.class, UserDO.class, false);
    @Getter
    private final BeanCopier do2dto = BeanCopier.create(UserDO.class, UserVO.class, false);

    public static void main(String[] args) {
        TestMain testMain = new TestMain();
        UserVO userVO = new UserVO();
        userVO.setId(1L);
        userVO.setUsername("name");
        System.err.println(testMain.dto2do(userVO));
    }

    UserDO dto2do(UserVO userVO) {
        UserDO userDO = new UserDO();
        this.getDto2do().copy(userVO, userDO, null);
        return userDO;
    }

    UserVO do2dto(UserDO userDO) {
        UserVO userVO = new UserVO();
        this.getDo2dto().copy(userDO, userVO, null);
        return userVO;
    }

    @Data
    private static abstract class Entity<ID> {
        private ID id;
    }

    @Data
    private static class UserVO extends Entity<Long> {
        private String username;
    }

    @Data
    private static class UserDO {
        private Long id;
        private String username;
    }
}

问题分析

spring boot项目采用idea开发环境没有任何问题,可以成功执行,而用maven打包后,用原生的java -jar命令执行就提示上面的错误信息,进一步用测试用例TestMain单独测试同样出现如上错误。初步分析,在spring boot中idea的启动命令和单独的TestMain中main方法的启动命令以及java -jar命令的执行环境不完全相同,到底哪儿不同呢?
查看idea控制台
spring boot的启动命令如下

"C:\Program Files\Java\jdk1.8.0_171\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:7587,suspend=y,server=n -javaagent:C:\Users\Furious\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -noverify -Dfile.encoding=UTF-8 -classpath ...

TestMain的启动命令如下

"C:\Program Files\Java\jdk1.8.0_171\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:7587,suspend=y,server=n -javaagent:C:\Users\Furious\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath ...

而java -jar命令只是单独的jar -jar springboot.jar
对比spring boot和TestMain的启动命令,很容易发现spring boot命令中多了-noverify参数,是不是这个参数在起作用呢?
执行java -jar -noverify springboot.jar 发现的确可以成功执行,ok,问题就出在noverify这个参数上。那是不是说以后使用BeanCopier出现这个问题时,就加noverify这个参数就万事大吉了呢?
先看了一下jvm参数中对verify的解释
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
意思是jvm虚拟机会对class文件的字节码进行校验,同时也可以配置成不校验字节码,但是,文档中明确说了,不推荐关闭字节码的校验
原文引用

Do not turn off verification as this reduces the protection provided by Java and could cause problems due to ill-formed class files.

那怎么办?同时,又搜到一篇博客,更加证实了不能随意使用noverify参数,点我查看博客

既然是字节码校验失败,字节码的来源是类文件,而类是我们自己定义的,那么问题应该就在类的定义里面,what?类定义还能出岔子?
初步观察UserDO,UserVO类,无非是UserVO是继承了Entity类,莫非是继承使得字节码校验失败,导致不能复制?
那么让UserVO不去继承Entity类试试勒,果然,不继承Entity类,完全没有任何问题,只不过没有id属性而已,但是继承本身就是java多态的一种体现,不应该出现因为继承而导致字节码校验失败啊。。。
再返回去看错误的详细信息,发现了新大陆

Type 'java/lang/Object' (current frame, stack[3]) is not assignable to 'java/lang/Long'

object不能分配long类型?一万头羊驼飘过。
不是指定了泛型ID的类型为long么,既然这样,那Entity类不用泛型勒
改成如下形式

    private static abstract class Entity {
        private Long id;
    }

再启动,完全没有问题,此时,找到问题根源所在。但是,缓过神来,Entity中ID为泛型,本身就是业务所需,也完全没有任何问题啊,不能强制人家固定id的属性就为Long啊。
此时,再回过头去看BeanCopier中copy的方法,发现第三个参数为Converter,既然支持类型转换,试试看,dto2do和dto2to方法分别换成如下形式

    @Getter
    private final BeanCopier dto2do = BeanCopier.create(UserVO.class, UserDO.class, true);

    UserDO dto2do(UserVO userVo) {
        UserDO userDO = new UserDO();
        this.getDto2do().copy(userVo, userDO, (value,clazz,setter) -> value);
        return userDO;
    }

运行TestMain的main方法,完全没有问题。
咦,为什么do2dto没有提示字节码错误勒,对比dto2do和do2dto成员变量,发现一个泛型是源对象,而另外一个则是目标对象,得出结论,只有源类继承了泛型类的时候,才会出现泛型丢失的情况。目标对象为泛型时,没有问题。

总结

当源类继承了泛型类的时候,使用BeanCopier的copy方法需要显示的设置Coverter转换器,否则,字节码校验不通过。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 用京东闪付支付公交费,可实现1分钱坐公交!不过,指定城市,具体详见京东闪付页面说明哦
    森1000阅读 365评论 0 0
  • 1.45#钢 优质碳素结构钢,最常用中碳调质钢 主要特征 最常用中碳调质钢,综合力学性能良好,淬透性低,水淬时易生...
  • 在我们上学时,好多同学都喜欢看书,老师不让看,可同学们都偷偷的看,现在孩子们不喜欢阅读,也不能把阅读作为自觉的行动...
    吕寨中心小学李松凡阅读 434评论 0 0
  • 城关镇西街小学四月推出读书月活动——读书小报。 “让每一个孩子畅游书海,让每一个孩子都能从浓浓的书香中汲取...
    bd98df883f00阅读 505评论 0 2
  • 棍棒之下出逆子 杨辉到现在仍然害怕看见星星,他说就是到夏天,也觉得星星像是被冰冻住似的…… (上)大案背后 事情是...
    育儿不累阅读 689评论 0 0