从解决一个java.lang.NoSuchMethodError想到的
今天在发布系统部署一个web app的时候,发现应用服务器(tomcat 7.0.26)不能正常启动,于是远程登陆到服务器上查看应用服务器的启动日志,在tomcat_home的logs/localhost.log.2014-03-17上发现了一段如下错误信息:
java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/Object;Ljava/lang/Throwable;)V
at org.apache.commons.logging.impl.SLF4JLocationAwareLog.error(SLF4JLocationAwareLog.java:225)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:319)
at com.alibaba.citrus.webx.context.WebxComponentsLoader.initWebApplicationContext(WebxComponentsLoader.java:117)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4779)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5273)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:895)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:871)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:615)
at com.taobao.tomcat.deploy.TomcatHostConfig.deployDirectory(TomcatHostConfig.java:482)
at com.taobao.tomcat.deploy.TomcatHostConfig.deployWarDirectory(TomcatHostConfig.java:366)
at com.taobao.tomcat.deploy.TomcatHostConfig.deployDirectories(TomcatHostConfig.java:339)
...后面的错误信息省略。
分析思路:
1、这是一个tomcat启动时的方法调用栈信息,结合web.xml中配置信息,发现配置了一个webx(一个阿里开源的webx框架)的WebxContextLoaderListener ,这个类的主要作用是在应用服务器启动的时候,初始化webapp的applicationContext,为webx的组件初始化提供支持。
(这不是我们叙述的重点,所以不重点讲述) 。
分析栈信息,定位到org.springframework.web.context.ContextLoader类的319行,
logger.error("Context initialization failed", ex);
这是一个捕获RuntimeException的catch块写error日志,这个logger对象的创建在270行
Log logger = LogFactory.getLog(ContextLoader.class);
查看类的import 声明 import org.apache.commons.logging.LogFactory ,结合对java日志知识的复习(点击这里) 。
可以知道采用的是commons-logging的日志接口(有可能是commons-logging,或者是jcl-over-slf4j的包引入的) ,
于是查看一下webapp的maven依赖树 ,把和日志相关的jar包都提取出来:
1、接口API包:
org.slf4j.slf4j-api-1.5.6.jar
slf4j-api-1.7.5.jar
2、日志框架包:
logback-core-1.0.13.jar
logback-classic-1.0.13.jar
jakarta.log4j-1.2.15.jar
apache-log4j-log4j-1.2.15.jar
log4j-log4j-1.2.15.jar
3、日志适配器包:
org.slf4j.slf4j-log4j12-1.5.6.jar
4、其他日志到slf4j日志的桥接包:
jcl-over-slf4j-1.7.5.jar
从以上包来看, 可以分析出一下结论:
1、slf4j-api有两个不同的版本 。
2、系统中既有logback,也有log4j,但是slf4j-api在静态绑定选择时,因为有slf4j-log4j包的存在,会出现多个绑定的问题。
3、commons-logging日志接口被jcl-over-slf4j桥接到slf4j日志接口了。
出现的异常是否和这个结论有关系呢 ?
回到栈信息 ,可以看到Log logger = LogFactory.getLog(ContextLoader.class); 这一行获取日志器的日志器工厂LogFactory 应该是在JCL-over-slf4j包内定义的, 查看源码,
public Log getInstance(String name) throws LogConfigurationException {
Log instance = loggerMap.get(name);
if (instance != null) {
return instance;
} else {
Log newInstance;
Logger slf4jLogger = LoggerFactory.getLogger(name);
if (slf4jLogger instanceof LocationAwareLogger) {
newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger);
} else {
newInstance = new SLF4JLog(slf4jLogger);
}
Log oldInstance = loggerMap.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
我们可以看到代码进入了标红的这一行,也就是利用一个适配器模式,把slf4jLogger转换成了org.apache.commons.logging.Log, 最后由这个LocationAwareLogger 抛出了一个错误信息,
java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/Object;Ljava/lang/Throwable;)V
而LocationAwareLogger 这个类是在slf4j-api中定义的,前面的分析我们看到打包后的WEB-INF下有两个slf4j-api的包 ,
其中在slf4j-api-1.5.6中的定义是这样的:
void log(org.slf4j.Marker marker, java.lang.String s, int i, java.lang.String s1, java.lang.Throwable throwable);
而在slf4j-api-1.7.5中的定义是这样的:
void log(org.slf4j.Marker marker, java.lang.String s, int i, java.lang.String s1, java.lang.Object[] objects, java.lang.Throwable throwable);
回顾《深入理解jvm》里面关于字节描述符的知识,我们知道错误信息提示的是需要找
log(Marker,String,int,String,Object[],Throwable) 这样的一个方法,但是没有找到,
说明编译时和运行时采用了不一样的jar .
再通过maven依赖树,查询到org.slf4j.slf4j-api-1.5.6.jar是被一个单点登录的jar依赖间接引入的,于是在父工程里面排除掉
org.slf4j.slf4j-api-1.5.6.jar间接依赖(顺便也排除掉了org.slf4j.slf4j-log4j12-1.5.6.jar这个适配器包) ,问题得到解决。
事后,我们再回顾出现的错误信息 ,java.lang.NoSuchMethodError ,其实这个类的继承体系结构
java.lang.Object
java.lang.Throwable
java.lang.Error
java.lang.LinkageError
java.lang.IncompatibleClassChangeError
java.lang.NoSuchMethodError 就已经告诉了我们出问题的线索了 ,比如ImcompatibleClassChangeError的英文注释是这么说的:
Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.
(大意是:当不兼容的类变化作用在了类的定义时,异常抛出。当前执行方法依赖的类定义发生了改变) 。 所以在查找问题时,异常本身的注释也是我们找问题的一个好办法。
----------------------------------------------------------------
回顾整个问题的解决过程,发现这次解决问题是采用 源码分析+maven依赖树的静态分析方法,我们有什么办法知道有问题的Class文件是jvm加载的哪个jar的么?
1)方法1:
jvm里面有一个跟踪classload的参数:
-XX:+TraceClassLoading
我们修改catalina.sh , 加上这个参数JAVA_OPTS=$JAVA_OPTS" "-XX:+TraceClassLoading ,然后把标准输出重定向到一个临时日志文件:
1>/tmp/logs/tomcat.log
在这个临时日志中,我们找到了
[Loaded org.slf4j.spi.LocationAwareLogger from file:~/eclipse_workspace/webapp/target/exploded/webapp.war/WEB-INF/lib/org.slf4j.slf4j-api-1.5.6.jar]
说明运行时的确是使用到了slf4j-1.5.6版本的Jar包,和编译classpath使用的jar包版本不一致。
2)方法2:
阿里的聚石写了一个housemd的工具 ,安装方法可以点击这里(https://github.com/CSUG/HouseMD) , 启动后运行如下命令attach到tomcat进程:
housemd `jps|grep Bootstrap|awk '{print $1}'`
然后loaded classname 就可以得到运行时类所在的jar .
要自己实现的话,比如我们要获取一个Test.class类是被哪个jar导入的,可以这样
URL url = Log.class.getClassLoader().getResource("Test.class");
url.getFile();
另外,还可以用loaded -h 命令查看类加载器树,
、
要自己实现的话可以这样:
for(ClassLoader cl = Log.class.getClassLoader(); cl != null ; cl = cl.getParent()){
System.out.println(cl.getName());
}
那么我们能否在应用打包的时候,就分析出存在jar冲突的地方呢 ?我的思路是这样的:
1、在应用打包成功以后,把应用的class的jar包拷贝到一个临时文件夹。
2、对这些jar包中的字节码文件,做如下操作:
1)使用asm分析出class 导入的class .
2)对每一个导入的class获取 build classpath下的jar列表,如果jar的个数大于等于2 ,则表明有jar冲突。
3)输出jar冲突信息到控制台。
目前,我已经在本机mac环境下实现了基本功能, 接下来的计划是做成maven插件,放到整个maven应用的build cycle中去。
从解决一个java.lang.NoSuchMethodError想到的的更多相关文章
- redis 集群java.lang.NoSuchMethodError:SpringJAR包版本冲突错误解决方法
项目中出现如下错误,记录下解决方法: org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exce ...
- java.lang.NoSuchMethodError的通用解决思路
NoSuchMethodError中文意思是没有找到方法,遇到这个错误并不是说依赖的jar包.方法不存在而找不到,这就类似于 ClassNotFoundException错误了,出现ClassNotF ...
- 记一次部署时报java.lang.NoSuchMethodError:javax.persistence.spi.PersistenceUnitInfo.getValidationMode()Ljavax / persistence / ValidationMode;的解决办法
楼主在部署war包的时候,本地启动不报错,服务器商报如下问题: Error creating bean with name 'entityManagerFactory' defined in clas ...
- java.lang.NoSuchMethodError: com.google.common.hash.HashFunction.hashInt(I)Lcom/google/common/hash/HashCode; 解决办法
今天在java 上运行spark查询的时候出现一个问题: java.lang.NoSuchMethodError: com.google.common.hash.HashFunction.hashIn ...
- Java程序中使用Spire Jar包报java.lang.NoSuchMethodError类型错误的解决方法
Jar包功能概述 使用Spire系列的Jar包可以操作Word.Excel.PPT.PDF.Barcode等格式的文件,分别对应使用的jar包是Spire.Doc for Java.Spire.XLS ...
- 【原】tomcat 7 启动报错:java.lang.NoSuchMethodError: javax.servlet.ServletContext.getSessionCookieConfig()Ljavax/servlet/SessionCookieConfig的解决
现象: tomcat 7 启动报错:java.lang.NoSuchMethodError: javax.servlet.ServletContext.getSessionCookieConfig() ...
- java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.isAsyncStarted()Z 的解决
jetty 9 嵌入式开发时,启动正常,但是页面一浏览就报错如下: java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest ...
- 关于java.lang.NoSuchMethodError: android.widget.RelativeLayout.setBackground的解决办法
今天用一个安卓4.0.4版本的手机测试手上的项目,发现logcat弹出这样一个提示“java.lang.NoSuchMethodError: android.widget.RelativeLayout ...
- Cloudera集群中提交Spark任务出现java.lang.NoSuchMethodError: org.apache.hadoop.hbase.HTableDescriptor.addFamily错误解决
Cloudera及相关的组件版本 Cloudera: 5.7.0 Hbase: 1.20 Hadoop: 2.6.0 ZooKeeper: 3.4.5 就算是引用了相应的组件依赖,依然是报一样的错误! ...
随机推荐
- CF 1117 E. Decypher the String
E. Decypher the String 链接 题意: 有一个字符串,一些操作,每次操作交换两个位置的字符,经过这些操作后,会得到新的字符串.给你新的字符串,求原来的串.可以有3次询问,每次询问给 ...
- 改革春风吹满地,安卓新系统Q上线腾讯WeTest
“刚要适配安卓派,Q就来了.” 3月14日谷歌推出了期待已久的Android Q的首个测试版本Android Q Beta 1 ,这是Android系统推出以来的第十个大版本. 安卓Q相比之前的版本, ...
- VBA_把相同行一列追加数据到一行
Sub Test() Dim rowsNum, i, j, equalRowsNum As Integer rowsNum = ActiveSheet.UsedRange.Rows.Count '获得 ...
- Rmdir方法
删除现有目录或文件夹. 语法 RmDir路径 所需的_路径_参数标识的目录或文件夹要移除的字符串表达式. _路径_可以包含驱动器. 如果没有指定驱动器, RmDir 将删除当前驱动器上的目录或文件夹. ...
- Educational Codeforces Round 61 (Rated for Div. 2) E. Knapsack
非常经典的dp题,因为1至8的最大公约数是840,任何一个数的和中840的倍数都是可以放在一起算的, 所以我只需要统计840*8的值(每个数字(1-8)的sum%840的总和),剩下都是840的倍数 ...
- LimeSDR在windows下使用Gqrx来接收FM广播
本文内容.开发板及配件仅限用于学校或科研院所开展科研实验! 淘宝店铺名称:开源SDR实验室 LimeSDR链接:https://item.taobao.com/item.htm?spm=a230r.1 ...
- Codeforces70 | Codeforces Beta Round #64 | 瞎讲报告
目录 前言 正文 A B C D E 前言 这个毒瘤的517 放了Div1 然后D题是昨天讲的动态凸包(啊喂!我还没来的及去写 结果自己想的是二分凸包 (当然没有写出来 写完前两题之后就愉快地弃疗 C ...
- Tomcat分析
最近闲来无事,总结了一下tomcat的一些知识,分享出来供大家参考,如有错误,请及时与我联系. 1. 入门示例:虚拟主机提供web服务 该示例通过设置虚拟主机来提供web服务,因为是入门示例,所以设置 ...
- join命令详解
基础命令学习目录首页 原文链接:https://www.cnblogs.com/agilework/archive/2012/04/18/2454877.html 功能说明:将两个文件中,指定栏位内容 ...
- [linux] lsyncd同步工具
环境说明: 192.168.56.101 同步源 192.168.56.102 同步目标 操作系统centos 7 lsyncd项目地址:https://github.com/axkibe/lsync ...