记Classloader引起的ClassCastException
这两天写了一个在应用启动时扫描Enum并保存到Map中的方法,在main方法中执行得没问题,但是放在Spring的ApplicationRunner里启动一直出现莫名的ClassCastException。
先看main方法中执行的测试代码:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class<?> enums = ClassUtils.loadClass("javis.upms.web.entity.UpmsUser$Enums");
DbColumnEnum[] invoke = ((DbColumnEnum[]) MethodUtils.invoke(enums, "values"));
for (DbColumnEnum e : invoke) {
System.out.println(e);
}
}
执行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
GENDER$MALE
GENDER$FEMALE
其中sun.misc.Launcher$AppClassLoader@18b4aac2
是在ClassUtils.loadClass()中打印出来的
ClassUtils.loadClass()代码:
public static Class<?> loadClass(String name) throws ClassNotFoundException {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader.toString());
return systemClassLoader.loadClass(name);
}
接下来将代码放在了Springboot的ApplicationRunner实现类中执行,关键代码如下
private static void initDBFieldEnumMap() {
String path = ClassLoader.getSystemResource("").getPath() + SCAN_PACKAGE;
List<String> innerEnumsClass = FileUtils.listInnerEnumsClass(path);
DB_FIELD_ENUM_MAP = new HashMap<>(innerEnumsClass.size());
innerEnumsClass.forEach(f -> {
Class<?> enums = null;
try {
enums = ClassUtils.loadClass(f);
String s = DbColumnEnum.class.getClassLoader().toString();
System.out.println(s); // 此处打印一下DbColumnEnum的classloader
DbColumnEnum[] invoke = ((DbColumnEnum[]) MethodUtils.invoke(enums, "values")); // 此处会抛出ClassCastException
for (DbColumnEnum e : invoke) {
String outerClassName = enums.getName().split("\\$")[0];
String outerClassSimpleName = outerClassName.substring(outerClassName.lastIndexOf(".") + 1);
String key = outerClassSimpleName + "_"
+ e.toString().split("\\$")[0].toLowerCase() + e.getValue();
System.out.println(key);
DB_FIELD_ENUM_MAP.put(key, e.getDesc());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
}
启动后出现异常日志:
java.lang.ClassCastException: [Ljavis.upms.web.entity.UpmsUser$Enums; cannot be cast to [Ljavis.upms.web.common.enums.DbColumnEnum;
打印classloader进行排查
sun.misc.Launcher$AppClassLoader@18b4aac2
org.springframework.boot.devtools.restart.classloader.RestartClassLoader@5daee66a
第一行是ClassUtils.loadClass打印出来的
第二行是DbColumnEnum.class.getClassLoader()
可以看到因为使用了springboot的devtools,所以整个上下文是使用了RestartClassLoader,
而ClassUtils.loadClass中使用的ClassLoader.getSystemClassLoader()是默认的系统ClassLoader,所以导致问题出现。
接下来把ClassUtils.loadClass代码调整如下:
public static Class<?> loadClass(String name) throws ClassNotFoundException {
ClassLoader systemClassLoader = Thread.currentThread().getContextClassLoader();
return systemClassLoader.loadClass(name);
}
与上下文ClassLoader一致,问题解决。
解决了这个问题对Java类加载机制有了进一步的了解,不过仍然需要继续深入学习。
网友评论