Caused by : java.lang.NoSuchMethodError

问题

相信大家在开发过程中一定经常遇到此类报错 : Caused by: java.lang.NoSuchMethodError 。
因此今天总结特地做个总结,希望能够解决此类问题,而不是这一个问题。
下面以我遇到的一个报错为例,说一下我对此类问题的解决方法 。
今天在升级了 Spring 版本之后,启动 Tomcat 报错,报错见下 :


日志

[Loaded javax.servlet.ServletContextEvent from file:/D:/apache-tomcat-8.5.34/lib/servlet-api.jar] 
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:498)
   at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
   at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
   at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
   at com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:468)
   at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
   at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
   at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
   at java.security.AccessController.doPrivileged(Native Method)
   at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1408)
   at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:498)
   at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
   at sun.rmi.transport.Transport$1.run(Transport.java:200)
   at sun.rmi.transport.Transport$1.run(Transport.java:197)
   at java.security.AccessController.doPrivileged(Native Method)
   at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
   at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
   at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
   at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
   at java.security.AccessController.doPrivileged(Native Method)
   at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at java.lang.Thread.run(Thread.java:748)

Caused by: java.lang.NoSuchMethodError: org.springframework.util.ReflectionUtils.accessibleConstructor(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
   at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:154)
 ... 46 more

查找问题

首先我们需要确定项目使用的是哪个 jar 包 。以 IntelliJ IDEA 为例 :

tomcat 启动的时候添加 jvm 参数 “-XX:+TraceClassLoading” , 操作如下图 :

Caused by : java.lang.NoSuchMethodError

Caused by : java.lang.NoSuchMethodError

配置之后,重启 tomcat ,控制台除了打印输出错误日志以外,还可以看到使用的 jar 包 :
Caused by : java.lang.NoSuchMethodError

看到以上错误输出是使用了 spring-core-3.2.10 版本的 jar 包导致的 。
因此问题为转换为: 在spring-core-3.2.10 jar 中的 ReflectionUtils 类里没有找到 accessibleConstructor 方法。
在工程里搜索 ReflectionUtils,如下图:
Caused by : java.lang.NoSuchMethodError
我们看到工程里有两个 ReflectionUtils ,一个是 spring-core-3.2.10 ,另一个是 spring-core-5.1.5 版本,分别进入这两个类,发现在 spring-core-5.15 中有 accessibleConstructor 方法,而 spring-core-3.2.10 中没有 accessibleConstructor 方法。

通过以上步骤初步得出结论,项目同时依赖了 spring-core-3.2.10 和 spring-core-5.1.5 。 IDE 在编译时使用了低版本的 spring-core-3.2.10 jar 包,而 jar 中没有accessibleConstructor 方法,因此抛出了 NoSuchMethodError 异常。


分析依赖树

接下来继续分析是 哪些项目依赖了 spring-core-3.2.10 和 spring-core-5.1.5 ? 是自己的工程还是引用的第三方工程依赖的呢 ?因此问题转换为分析依赖关系。
如果项目依赖较简单,可以直接在 IDEA 右侧找到依赖树, 如果是使用 Gradle 构建项目就点击 Gradle, 如果是 Maven 构建项目就点击 Maven 。

Caused by : java.lang.NoSuchMethodError

下图中左侧有箭头的含义是这个库引用了其他 jar 包,可以点击箭头展开。

Caused by : java.lang.NoSuchMethodError

这样看的比较直观,但是有个缺点,一个大工程所依赖的 jar 包可能是几十或上百个的。因此,有时我们需要通过命令行分析依赖树,以下分析依赖树以 Gradle 为例 :
如果项目简单,仅仅有一个工程,没有依赖其他 moudle ,
进入工程 build.gradle 所在目录,执行以下命令。

gradlew dependencies 

在控制台可以到整个项目的依赖树,如下图:
Caused by : java.lang.NoSuchMethodError

如果工程比较庞大,依赖了很多 module ,仅仅想分析某一 module 的依赖 :

gradlew   :submoduleName:dependencies  >  dependencies.log

其中 > dependencies.log 是将日志导出到命令执行目录的 dependencies.log 中,原因是因为有时候 console 中日志太多,console 无法跳的太快,无法全部分析,因此导入到文件中。分析出的依赖树如下:

Caused by : java.lang.NoSuchMethodError

可以看到 org.springframework:spring-core:5.1.5.RELEASE 被 3.2.10.RELEASE 覆盖 ,因此会报 Caused by: java.lang.NoSuchMethodError 的错 。

问题找到了,那么解决就很简单了。


总结

整个问题的分析过程到此结束,解决问题的方案无非下面两种:

  1. 去除不必要的依赖
  2. 统一使用的版本号
    因为我使用的是 Gradle ,因此使用的命令就是 force 或 exclude 。 如果对这两个命令不熟悉, 可参考下我以前的一篇 文章
    如果你使用的 Maven , 分析问题的步骤大致相同,因为我对 Maven 不太熟悉,不在此赘述。