概述
一、现象
本地开发环境在开发新功能的过程中突然出现了诡异的ClassCastException,之所以称之为诡异,是因为出现了对象强转自身所属类异常。 发生的场景:项目首次接入memcache,在通过泛型方法取值时,虽然取到的值和接收的值是同一个类,但是却出现强转异常。(自己无法强转自己 ⊙﹏⊙∥∣°) 抛出异常:java.lang.ClassCastException: com.zhqy.bean.Person cannot be cast to com.zhqy.bean.Person
环境如下:
macos mojave
idea 2018.2.1
springboot、memcache、spring-boot-devtools
二、问题排查
1、怀疑泛型方法有问题,但是方法简单到无法怀疑
// 代码功能简述:将memcache的对象反序列化成对应的实体类
// 简单代码如下
public <T> T get(String key) {
try {
return (T) mc.get(key);
} catch (Exception e) {
log.error(EXCEPTION, e);
}
return null;
}
2、怀疑是工具方法中的泛型有问题,导致无法正确转换 使用Junit和main方法运行具体的工具方法,发现结果一直是正确的,未出现一次ClassCastException。
三、问题解决
一般的排查手段已经无法解决问题,最后还是同事「张帆」提出,可能是因为spring-boot-devtools热部署功能使用了自定义ClassLoader导致的问题。
验证理论的过程如下:
1、先简单准备自定义classLoader的实例代码,代码如下:
// 调用方法:getPersonUseMyClassLoader
Person person = test.getPersonUseMyClassLoader();
// 测试类中的getPersonUseMyClassLoader方法
// 当项目中使用了自定义CLassLoader的情况,一些泛型方法就会出问题
// 例如:项目中使用了spring-devtool,memcache的get方法如果用了泛型方法,就会出问题
// 此方法模拟memcache的get泛型方法
public <T> T getPersonUseMyClassLoader() {
MyClassLoader loader = new MyClassLoader();
Class> aClass = loader.findClass(Person.class.getName());
try {
Object obj = aClass.newInstance();
return (T) obj;
} catch (Exception e) {
logger.error(String.format("错误:%s", e));
}
return null;
}
// MyClassLoader类
public class MyClassLoader extends ClassLoader {
private Logger logger = Logger.getLogger(MyClassLoader.class);
@Override
public Class> findClass(String name) {
String myPath = "file://" + MyClassLoader.class.getResource("/").getPath().replace("test-classes", "classes") + name.replaceAll("\.", "/") +".class";
logger.debug(String.format("class file path:%s", myPath));
byte[] cLassBytes = null;
Path path = null;
try {
path = Paths.get(new URI(myPath));
cLassBytes = Files.readAllBytes(path);
return defineClass(name, cLassBytes, 0, cLassBytes.length);
} catch (IOException | URISyntaxException e) {
logger.error(String.format("错误:%s", e.getMessage()));
}
return null;
}
}
2、根据抛出的强转异常,将示例代码的第一句进行如下修改:
Person person = test.getPersonUseMyClassLoader();
改为
Object obj = test.getPersonUseMyClassLoader();
logger.info(String.format("obj.equals(Person.class):%s", obj.getClass().equals(Person.class)));
// 输出false
logger.info(String.format("obj instanceof Person:%s", obj instanceof Person));
// 输出false
logger.info(String.format("obj 的 classLoader:%s", obj.getClass().getClassLoader()));
// 输出com.zhqy.classLoad.MyClassLoader
logger.info(String.format("Person 的 classLoader:%s", Person.class.getClassLoader()));
// 输出sun.misc.Launcher$AppClassLoader
根据修改后obj instanceof Person的结果可知,obj不是Person的对象。(很蛋疼的结果,因为理论上obj确实是Person类的对象) 在这种情况下,分析示例代码的字节码得知,instanceof结果为false和泛型方法出现强转异常,是因为泛型方法仅仅只是省去人为指定强转类型而已,并不是没有强转的那一步。详见字节码文件内容:
// getPersonUseMyClassLoader() 方法返回一个 Object 类型的对象
INVOKEVIRTUAL com/zhqy/bean/Person.getPersonUseMyClassLoader ()Ljava/lang/Object;
// 检查是否可以转换成Person对象,如果不可以转换则会抛出异常
CHECKCAST com/zhqy/bean/Person
四、结论
在示例代码中,虽然被强转对象和接收的对象是同一个,但是因为classLoader不是同一个,在CHECKCAST时就会抛出ClassCastException异常。 解决办法
1、创建META-INF/spring-devtools.properties文件
2、将memcache也配置为使用spring-boot-devtools的ClassLoader
#spring-devtools.properties文件示例
restart.include.memcachedclient=/com.zhqy.spat.memcachedclient-.*jar
参考文档: INSTANCEOF、CHECKCAST关键字解释 [张振阳] spring-boot-devtools 不同ClassLoader引起的问题 spring-boot-devtools文档,Customizing the Restart Classloader部分
(完)
推荐阅读
Java面试通关要点 汇总集【最终版】
如何合理的设计代码分层
微服务初级设计指南
良好的 API 设计指南
HTTPS 项目实战指南
常用性能监控指南
保证服务4个9的可用性的核心思路
一次应用 CPU 飙高的血案排查过程
探寻 Redis 超时元凶的全过程剖析
探寻 Redis 内存诡异增长的元凶
如何健壮你的后端服务
消息中间件选型分析
更多精彩文章,尽在「服务端思维」!
最后
以上就是寂寞楼房为你收集整理的logger没有error 方法_探寻泛型方法ClassCastException元凶二、问题排查三、问题解决四、结论的全部内容,希望文章能够帮你解决logger没有error 方法_探寻泛型方法ClassCastException元凶二、问题排查三、问题解决四、结论所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复