问题描述
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转换器,否则,字节码校验不通过。
网友评论