问题来源
记录今天遇到的一个NoClassNotFoundException
.
2023-11-10 11:08:52,386 ERROR [dispatcherServlet]:175 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: ma/glasnost/orika/MapperFactory] with root cause
java.lang.ClassNotFoundException: ma.glasnost.orika.MapperFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[?:1.8.0_301]
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_301]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[?:1.8.0_301]
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_301]
at com.patsnap.data.common.lake.query.LakeQueryTool.post(LakeQueryTool.java:373) ~[data-common-cloudApi-Dev.0.2.5-20231110.020456-22.jar:Dev.0.2.5-SNAPSHOT]
at com.patsnap.data.common.lake.query.LakeQueryTool.getDPPToken(LakeQueryTool.java:120) ~[data-common-cloudApi-Dev.0.2.5-20231110.020456-22.jar:Dev.0.2.5-SNAPSHOT]
at com.patsnap.data.common.lake.query.LakeQueryTool.athenaQueryLogIdAsync(LakeQueryTool.java:179) ~[data-common-cloudApi-Dev.0.2.5-20231110.020456-22.jar:Dev.0.2.5-SNAPSHOT]
at com.patsnap.data.common.lake.query.LakeQueryTool.athenaQuery(LakeQueryTool.java:312) ~[data-common-cloudApi-Dev.0.2.5-20231110.020456-22.jar:Dev.0.2.5-SNAPSHOT]
at com.patsnap.data.common.lake.query.LakeQueryTool.athenaQuery(LakeQueryTool.java:298) ~[data-common-cloudApi-Dev.0.2.5-20231110.020456-22.jar:Dev.0.2.5-SNAPSHOT]
at com.patsnap.data.exploration.utils.DppApi.athenaQuerySync(DppApi.java:171) ~[classes/:?]
at com.patsnap.data.exploration.controller.DataExplorationController.executeAthenaSql(DataExplorationController.java:207) ~[classes/:?]
事情是酱紫的~今天在公司写了一个公共模块Common-A,然后需要把这个Common模块用于其他的业务项目中,A模块中需要使用反射工具类来进行对象字段直接的get,set赋值(没有引入spring,只是使用java本身自带的反射类)。但是A模块为业务模块,像反射这种和业务无关的功能应该写在与业务无关的Common的moudle下,因此我在Common-core模块下,引入了反射包orika-core
。maven依赖如下。
<dependencies>
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
</dependency>
...
</dependencies>
Common-core又是依赖父module, Common-parent的,因此orika-core这个依赖的版本也是写在 Common-parent里面进行统一版本管理的,如下。
<properties>
<ma.glasnost.orika.orika-core.version>1.4.6</ma.glasnost.orika.orika-core.version>
...
</properties>
<dependencyManagement>
<dependencies>
<!-- orika-core: 反射工具-->
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>${ma.glasnost.orika.orika-core.version}</version>
</dependency>
....
</dependencies>
</dependencyManagement>
现在大致依赖情况是这样的。
公司的业务项目 --> Common-A —> Common-core(写了个反射工具类,引入了orika-core, orika-core的版本管理在Common-parent里面(这是重点后面要考!))
按理来说maven默认的dependency,父项目中有的依赖,在子项目中也会引入,就是说依赖了Common-A的业务项目也应该会有orika-core这个jar包才对(幻想很美好,现实很骨感)。
排查过程
一开始遇到这个Exception很懵逼,主要是因为快下班了。。。。。,后来洗把脸冷静了下,慢慢开始屡屡逻辑。我现在业务代码执行的必经之路上打了断点调试,看看当前的ClassLoader中有哪些classes,虽然知道应该没有ma.glasnost.orika.MapperFactory
这个类了,但还是想看看。然后
犹豫了片刻,嗯。。。。,8000多个类,这得找到猴年马月,然后就想着在业务的毕竟之路上,写一段逻辑来筛选出我想要的class类。代码如下。
ClassLoader classLoader = lakeQueryTool.getClass().getClassLoader();
Field classes = classLoader.getClass().getDeclaredField("classes");
classes.setAccessible(true);
Vector<Class> o = (Vector<Class>) classes.get(classLoader);
for (Class aClass : o) {
if (aClass.getCanonicalName().contains("patsnap")) {
System.out.println(aClass.getCanonicalName());
}
}
但是好像没办法获取到classes
这个字段的内容。。。。是我在异想天开
后来想到了利用ClassLoader的url来搜寻项目中jar包,然后查看jar包内class的方法,代码入下:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
URL[] urls = urlClassLoader.getURLs();
for (URL url : urls) {
if (url.toString().contains("patsnap")) {
JarFile jarFile = new JarFile(url.getFile());
jarFile.stream().filter(entry -> entry.getName().endsWith(".class")).forEach(entry -> {
String className = entry.getName().replace("/", ".").replace(".class", "");
System.out.println(className);
});
System.out.println(url);
}
}
上面的代码成功的实现了,我的想法,看到了当前项目中所依赖的和公司有关的class类,然后我也寻找了ma.glasnost.orika.MapperFactory
这个类。确实是没有找到
然后想到是不是在maven打包的时候压根就没有把这个依赖打包到项目里面,开始找pom.xml的茬,终于发现了,在我业务项目所依赖的Common-core的pom.xml里面的orika-core依赖没有引入成功,因为没有版本号,版本号是在Common-parent这个父项目里面统一进行管理的,但是我maven打包install的时候我只install了Common-A和Common-core,Common-parent的pom还是之前的,之前并没有这个版本号,这个版本号时候后来加入的。。。。。。
因此破案了,就是我maven,install的时候没有install, Common-parent这个模块导致,没有导入orika-core的相关依赖。
虽然但是很激动的,主要是自己一步步排查找到了问题所在(绝对不是因为可以下班了)。
经此之后,写公共模块,如果改动好几个模块一定要仔细,install,deploy的时候一定要把改动的模块都关照到。