记Classloader引起的ClassCastExceptio

2018-06-24  本文已影响0人  JavisChen

记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类加载机制有了进一步的了解,不过仍然需要继续深入学习。

上一篇 下一篇

猜你喜欢

热点阅读