解决方法

  1. 通过POM文件排查包冲突
  2. 安装IDEA的插件  Maven Helper
  3. 定位到编译WAR包的POM文件(我们框架定义的在Deploy模块中)

排除Jar包冲突的工具_Jar
4. 在搜索框中,输入搜索内容,点击右键可以看到选相框

  • 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使用的是双亲委派加载机制,通过查看ClassLoader类,可以对此有所了解。

类被加载成功后,将被放入到内存中,内存中存放Class实例对象。 可以通过jvm 启动命令:-verbose 参数或者 -XX:+TraceClassLoading

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 首先,检查 class 是否已经被加载Class<?> c = findLoadedClass(name);if (c == null) {// 如果没有被加载long t0 = System.nanoTime();try {if (parent != null) {// 寻找 parent 加载器c = parent.loadClass(name, false);
                } else {// 如果父加载器不存在,则委托给启动类加载器加载c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.// 如果仍然无法加载,才会尝试自身加载long t1 = System.nanoTime();
                c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }if (resolve) {
            resolveClass(c);
        }return c;
    }
}复制代码

类加载顺序

从代码中了解到,如果某个名字的类被加载后,类加载器是不会再重新加载,所以我们的问题根本原因可以是出现在:
先加载了org.slf4j包的org.slf4j.impl.StaticLoggerBinder,同名的ch.qos.logback包下的StaticLoggerBinder类没有被加载

总结:

一般jvm启动的时候报出一下错误多数与依赖包冲突有关

  • java.lang.ClassNotFoundException:类型转换错误
  • java.lang.NoSuchMethodError:找不到特定方法,如果有两个同名的包但是不同版本,例如:xxx-1.1和xxx-1.2包同时存在,先加载了1.1版本的类,但是1.2版本中才提供了新方法,导致提示找不到特定方法
  • java.langNoClassDefFoundError

提前预防

  • 使用工具检查依赖冲突

冲突检测插件:maven-enforcer-plugin 引用新的第三方依赖(工具包或者框架包),通过Maven插件检查一下conflict依赖,提前进行Exclude

  • 统一服务器版本

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