最近在搞一个动态加载外部jar包,进行动态加载和调用的项目,外部包的类加载用继承于URLClassLoader类加载器的一个自定义类加载器,为的是打破原始的双亲委派机制。
项目内部类就用项目自己的默认类加载器,最开始用的是ClassLoader.getSystemClassLoader()即AppClassLoader。ok,然后再IDE用启用测试,一切正常,内部类用AppClassLoader加载,外部类用自定义类加载器加载都成功了。
结果发布到测试环境,测试时发现一堆java.lang.NoClassDefFoundError,分析ClassLoader源码良久,没有头绪,因为本地IDE各种加载都是好的,上了测试环境就各种类加载失败。
于是决定maven打包,然后使用java -jar的方式在本地试下。结果发现,也报NoClassDefFoundError。那就是打包启动方面应该存在差异。通过日志分析出是系统默认加载器 加载内部类时NoClassDefFoundError,于是尝试使用当前类的类加载器即 xxx.class.getClassloader,并打印。发现java -jar方式启动,此处打印的系统默认加载器是springboot的LaunchedURLClassLoader,而不是APPClassLoader。问题就出在这里,经优化当前系统类加载器用Thread.currentThread().getContextClassLoader()来加载内部类,问题就解决了。
Java -Jar 和IDE里启动Sprintboot 有什么区别
Java -Jar是以FAT JAR的方式用LaunchedURLClassLoader来load class。而在IDE中则是直接以ApplicationClassLoader来load的。这种差别会导致调用classloader.getResourceAsStream()得到不一样的结果,这是因为FAT JAR启动时,LaunchedURLClassLoader的load的urls并没有FAT JAR本身,如abc-0.0.1-SNAPSHOT.jar, 但是应用中的src/main/resources/META-INF/resources目录被打包到了FAT JAR里,也就是abc-0.0.1-SNAPSHOT.jar!/META-INF/resources,这样这些resource也就不会被访问到了
这里参考了一位大神的文章,非常有用,聊一聊Springboot的类加载机制 - 简书