https://www.sevenyuan.cn/

如果程序运行的好好的,换台主机有如下非常,你须要想一想悄悄!

冲突提示信息java.lang.ClassNotFoundException:类型转换缺点,这个报错跟我这次碰着的一样,本该当引入的是 logback 包的类,但是实际引入的是 slf4j 下的同名类,导致类型转换缺点java.lang.NoSuchMethodError:找不到特定方法,如果有两个同名的包但是不同版本,例如 xxx-1.1和 xxx-1.2包同时存在,先加载了 1.1 版本的类,但是 1.2 版本中才供应了新方法,导致提示找不到特定方法java.lang.NoClassDefFoundError起因

运用从 jdk7 升级到 jdk8,终于可以用上新特性的语法进行代码编写,通过几轮开拓、测试和验证后,在上预发环境时,运用溘然无法启动,查看 tomcat 报错缘故原由,创造是 类转换失落败 ClassCastException

jsp文件报错multiple转jar包冲突破绽统一份代码有的主机运行正常有的掉败 Java

报错缘故原由

Class path contains multiple SLF4J binding

23-May-201916:04:25.300INFO[localhost-startStop-1]org.apache.jasper.servlet.TldScanner.scanJarsAtleastoneJARwasscannedforTLDsyetcontainednoTLDs.EnabledebugloggingforthisloggerforacompletelistofJARsthatwerescannedbutnoTLDswerefoundinthem.SkippingunneededJARsduringscanningcanimprovestartuptimeandJSPcompilationtime.SLF4J:ClasspathcontainsmultipleSLF4Jbindings.SLF4J:Foundbindingin[jar:file:/home/admin/xxx/WEB-INF/lib/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J:Foundbindingin[jar:file:/home/admin/xxx/WEB-INF/lib/logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J:Seehttp://www.slf4j.org/codes.html#multiple_bindingsforanexplanation.SLF4J:Actualbindingisoftype[org.slf4j.impl.Log4jLoggerFactory]报错缘故原由

org.slf4j.impl.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext

org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'cn.com.xxx.framework.log.integration.LogbackInitializer#0'definedinclasspathresource[spring/spring-log-init.xml]:Invocationofinitmethodfailed;nestedexceptionisjava.lang.ClassCastException:org.slf4j.impl.Log4jLoggerFactorycannotbecasttoch.qos.logback.classic.LoggerContext...Causedby:java.lang.ClassCastException:org.slf4j.impl.Log4jLoggerFactorycannotbecasttoch.qos.logback.classic.LoggerContext#出问题的加载地方atch.qos.logback.ext.spring.LogbackConfigurer.initLogging(LogbackConfigurer.java:72)atcn.com.xxx.framework.log.integration.LogbackInitializer.init(LogbackInitializer.java:49)atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)atjava.lang.reflect.Method.invoke(Method.java:498)atorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1706)atorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1645)atorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)...26more23-May-201915:59:12.398SEVERE[localhost-startStop-1]org.apache.catalina.core.StandardContext.startInternalOneormorelistenersfailedtostart.Fulldetailswillbefoundintheappropriatecontainerlogfile查看报错代码

publicstaticvoidinitLogging(Stringlocation)throwsFileNotFoundException,JoranException{StringresolvedLocation=SystemPropertyUtils.resolvePlaceholders(location);URLurl=ResourceUtils.getURL(resolvedLocation);LoggerContextloggerContext=(LoggerContext)StaticLoggerBinder.getSingleton().getLoggerFactory();loggerContext.reset();newContextInitializer(loggerContext).configureByResource(url);}

可以看到,通过 StaticLoggerBinder.getSingleton().getLoggerFactory() 获取 logger 高下文这段代码报错了,通过仔细定位,创造了有两个 StaticLoggerBinder 类

更主要的是,他们两兄弟竟然虽然不是同一个 jar 包,但是包路径和名称都千篇一律!


由于我们须要的是 logback 包,而不是 slf4j-log4j12 包,以是须要打消掉 slf4j-log4j12 依赖。

详细:Maven从入门到放弃

办理方法

① 通过 POM 文件排查包冲突

② 安装 IDEA 的插件 Maven Helper

③ 定位到编译 WAR 包的 POM 文件(我们框架定义的在 Deploy 模块中)

④ 在搜索框中,输入搜索内容,点击右键可以看到选项框

Jump To Source(跳转到源文件处)Exclude(打消掉)

例如我点击了 Exclude ,就能看到 pom 文件中,这个依赖就被打消掉了

<dependency><groupId>cn.com.xxx</groupId><artifactId>framework-conf-client</artifactId><version>${xqy.framework.version}</version><exclusions><exclusion><artifactId>slf4j-log4j12</artifactId><groupId>org.slf4j</groupId></exclusion></exclusions></dependency>

打消依赖后,提交代码,重新打包,支配一条龙,顺利启动~

思考

包冲突办理是大略的,通过 maven 插件可以精确找到依赖,然后进行 Exclude,可是在本地开拓、测试环境都没有涌现的问题,却在预发环境涌现了,以是打消了业务逻辑代码的缘故原由,大略考虑了几个成分和缘故原由:

jdk 版本tomcat 版本类加载机制第三方 jar 相互依赖

由于 jdk 和 tomcat 这两者没有明显的报错缘故原由,以是先去排查类的加载机制

类加载机制

复习一下类加载机制

我们写的 Java 运用代码,一样平常是通过 App ClassLoader 运用加载器进行加载,它不会自己先去加载它,而是通过 Extension ClassLoader 扩展类加载器进行加载(个中扩展类加载器又会去找 Bootstrap ClassLoader 启动类加载器进行加载),只有父加载器无法加载情形下,才会让下级加载器进行加载。

引用自zthgreat

当一个ClassLoader实例须要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检讨的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。
如果它们都没有加载到这个类时,则抛出ClassNotFoundException非常。

ClassLoader

Java 利用的是双亲委派加载机制,通过查看 ClassLoader 类,可以对此有所理解。

类被成功加载后,将被放入到内存中,内存中存放 Class 实例工具。

protectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//First,checkiftheclasshasalreadybeenloaded//首先,检讨class是否已经被加载Class<?>c=findLoadedClass(name);if(c==null){//如果没有被加载longt0=System.nanoTime();try{if(parent!=null){//探求parent加载器c=parent.loadClass(name,false);}else{//如果父加载器不存在,则委托给启动类加载器加载c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){//ClassNotFoundExceptionthrownifclassnotfound//fromthenon-nullparentclassloader}if(c==null){//Ifstillnotfound,theninvokefindClassinorder//tofindtheclass.//如果仍旧无法加载,才会考试测验自身加载longt1=System.nanoTime();c=findClass(name);//thisisthedefiningclassloader;recordthestatssun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if(resolve){resolveClass(c);}returnc;}}类加载顺序

从代码中理解到,如果某个名字的类被加载后,类加载器是不会再重新加载,以是我们的问题根本缘故原由可以是涌如今:

先加载了 org.slf4j 包的 org.slf4j.impl.StaticLoggerBinder,同名的 ch.qos.logback 包下的 StaticLoggerBinder 类没有被加载

扩展:看完这篇后,别再说你不懂JVM类加载机制了~

通过查阅文章:

跟JAR文件的文件名有关。
按照字母的顺序加载JAR文件。
有了这个类往后,后面的类则不会加载了。

jvm 加载包名和类名相同的类时,先加载classpath中jar路径放在前面的,包名类名都相同,那jvm没法区分了,如果利用ide一样平常情形下是会提示发生冲突而报错,若不报错,只有第一个包被引入(在classpath路径下排在前面的包),第二个包会在classloader加载类时判断重复而忽略。

查看加载顺序

在 jvm 启动脚本中,添加 -verbose 参数或者 -XX:+TraceClassLoading

[Loadedjava.lang.CloneNotSupportedExceptionfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.lang.Thread$Statefrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$NavigableSubMapfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$AscendingSubMapfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$NavigableSubMap$EntrySetViewfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$AscendingSubMap$AscendingEntrySetViewfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$NavigableSubMap$SubMapIteratorfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar][Loadedjava.util.TreeMap$NavigableSubMap$SubMapEntryIteratorfrom/Users/jingqi/.jrebel/bootcache/jrebel-bootstrap-89f567048120ef32b965233b5ba2f7ca.jar]

之前在本地开拓中,IDEA 优化先加载了 ch.qos.logback 的 StaticLoggerBinder 类,然后后面的 org.slf4j 包下的同名类就没有被加载。

但这样也有个不明白,按理说加载顺序按照字母顺序加载,预发环境还是能够跟本地开拓一样,加载到我们须要的类。
实际上,加载器加载到的是另一个类,导致运用无法启动。

通过查找资料

问题便是jar的加载顺序问题,而这个顺序实际上是由文件系统决定的,linux内部是用inode来指示文件的。

这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。

Unix/linux系统内部不该用文件名,而利用inode号码来识别文件。
对付系统来说,文件名只是inode号码便于识别的别称或者外号。

为了验证 inode 是否是问题的缘故原由,我做了以下测试:

inode 测试加载顺序本地 Tomcat8 测试(正常启动)

将之前在 uat 环境有问题的代码版本重新打包,不该用 idea 工具,直接用 tomcat8 启动,并且在 catalina.sh 脚本中加入类加载打印参数 -XX:+TraceClassLoading

catalina.sh

#RegistercustomURLhandlers#DothisheresocustomURLhandles(specifically'war:...')canbeusedinthesecuritypolicyJAVA_OPTS="$JAVA_OPTS-XX:+TraceClassLoading"

查看 catalina.out 输入日志,创造先加载的是 logback 包中 StaticLoggerBinder

在 WEB-INF/lib 下比较 inode 大小(正常解压和启动 logback < slf4j)

ll-ilogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar34153162-rw-r-----1jingqistaff274K812018logback-classic-1.1.3.jar34153180-rw-r-----1jingqistaff9.5K10172018slf4j-log4j12-1.6.1.jar本地 Tomcat8 测试(删包,先添加 slf4j,后添加 logback)清理掉 catalina.out重新上传包比较 inode 大小重新启动,查看类加载日志

比较 inode 大小(创造 slf4j < logback)

#ll-ilogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar34162396-rw-r--r--1jingqistaff274K812018logback-classic-1.1.3.jar34162361-rw-r--r--1jingqistaff9.5K10172018slf4j-log4j12-1.6.1.jar

重新启动后,查看 catalina.out 日志,创造类加载顺序与之前的同等,运用也能正常启动,以是本地开拓无法复现 =-=

在 uat 环境做事器测试

在 WEB-INF/lib 路径下,先将这两个包删掉,考试测验有不同的上传顺序,仿照 tomcat 解压 war 包

[admin@uat-96-0-248lib]$rmlogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar[admin@uat-96-0-248lib]$rz[admin@uat-96-0-248lib]$#Received/Users/jingqi/Downloads/slf4j-log4j12-1.6.1.jar[admin@uat-96-0-248lib]$rz[admin@uat-96-0-248lib]$#Received/Users/jingqi/Downloads/logback-classic-1.1.3.jar#第一次上传顺序1、slf4j-log4j12-1.6.1.jar2、logback-classic-1.1.3.jar# inode 比较:slf4j < logback[admin@uat-96-0-248lib]$ll-ilogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar396731-rw-r--r--1adminadmin2809288月12018logback-classic-1.1.3.jar394075-rw-r--r--1adminadmin975310月172018slf4j-log4j12-1.6.1.jar[admin@uat-96-0-248lib]$rmlogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar[admin@uat-96-0-248lib]$rz[admin@uat-96-0-248lib]$#Received/Users/jingqi/Downloads/logback-classic-1.1.3.jar[admin@uat-96-0-248lib]$rz[admin@uat-96-0-248lib]$#Received/Users/jingqi/Downloads/slf4j-log4j12-1.6.1.jar#第二次上传顺序1、logback-classic-1.1.3.jar2、slf4j-log4j12-1.6.1.jar# inode 比较:logback < slf4j[admin@uat-96-0-248lib]$ll-ilogback-classic-1.1.3.jarslf4j-log4j12-1.6.1.jar394075-rw-r--r--1adminadmin2809288月12018logback-classic-1.1.3.jar396731-rw-r--r--1adminadmin975310月172018slf4j-log4j12-1.6.1.jar

分别测试了两种场景,创造只要这两个包都存在的情形下,无论 inode 两者的大小,都是先加载了 slf4j 包的类,导致启动报错

测试结束

通过多种测试场景,创造本地开拓、测试环境都无法复现的问题,在 uat 环境下,只要这两个包同时存在,都会启动报错,系统版本是这个:

[admin@uat-96-0-248lib]$cat/proc/versionLinuxversion3.10.0-514.26.2.el7.x86_64(builder@kbuilder.dev.centos.org)(gccversion4.8.520150623(RedHat4.8.5-11)(GCC))#1SMPTueJul415:04:05UTC2017#jdk和tomcat版本Serverversion:ApacheTomcat/8.5.34Serverbuilt:Sep4201822:28:22UTCServernumber:8.5.34.0OSName:LinuxOSVersion:3.10.0-514.26.2.el7.x86_64Architecture:amd64JVMVersion:1.8.0_192-b12JVMVendor:OracleCorporation

末了在官方文档创造这个:

The order in which the JAR files in a directory are enumerated in the expanded class path is not specified and may vary from platform to platform and even from moment to moment on the same machine. A well-constructed application should not depend upon any particular order. If a specific order is required, then the JAR files can be enumerated explicitly in the class path.

大意为:同一个目录下,jvm加载jar包顺序是无法担保的,每个别系的都不一样,乃至同一个别系不同的时候加载都不一样。

于是乎,我也不纠结某台做事器上的类加载顺序,在开拓阶段就先将这个包冲突的情形,给提前办理掉~

总结冲突提示信息java.lang.ClassNotFoundException:类型转换缺点,这个报错跟我这次碰着的一样,本该当引入的是 logback 包的类,但是实际引入的是 slf4j 下的同名类,导致类型转换缺点java.lang.NoSuchMethodError:找不到特定方法,如果有两个同名的包但是不同版本,例如 xxx-1.1和 xxx-1.2包同时存在,先加载了 1.1 版本的类,但是 1.2 版本中才供应了新方法,导致提示找不到特定方法java.lang.NoClassDefFoundError排查思路

1、查看 catalina.sh 堆栈信息,找到有问题的类

2、通过 IDEA ,在打包的 POM 文件中,利用 Maven Helper 插件找出冲突的依赖,确定项目须要的 jar 包,Exclude 掉不须要的依赖。

提前预防

1、利用工具检讨依赖冲突

冲突检测插件 :maven-enforcer-plugin

引用新的第三方依赖(工具包或者框架包),通过 Maven 插件检讨一下 conflict 依赖,提提高行 Exclude

2、统一做事器版本

在测试阶段,准备好和生产环境一样的做事器,提提高行测试,避免依赖冲突的 WAR 包上传莅临盆环境,例如我们有一台 UAT 做事器,与生产环境一样配置,提前测试,暴露风险和解决问题~