开篇
最近因为一些原因遇到了一些jar包冲突的实际问题,包括tomcat无法加载某lib包,hadoop上mr任务依赖lib版本问题,tomcat依赖包找不到等等。
这些问题归结起来能够很好的检验几个关键问题:1、双亲委派原则的理解;2、maven依赖传递的原则;3、java classpath路径先后顺序问题。
双亲委派原则
说明:
- 1、首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
- 2、如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
- 3、如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
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 stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
实际案列
- 如果指定名称的类已经加载过了就不会再次加载,这个问题属于双亲委派范围,存在两种情况会遇到这个问题。
- 1、多版本jar包依赖当中,由于低版本jar包中类已经加载导致高版本jar包中同名的类无法加载,从而导致使用了高版本的jar包依赖的函数无法找到,因为该函数只在高版本中存在。
- 2、不同的jar包却存在同名路径的类,如package+class的路径是完全一致的,那就会导致两个不同的jar包的同名类只有一个类会被加载,从而同样找不到函数问题。
maven依赖原则
maven的依赖顺序
- 1、依赖路径最短优先原则
一个项目Demo依赖了两个jar包,其中A-B-C-X(1.0) , A-D-X(2.0)。由于X(2.0)路径最短,所以项目使用的是X(2.0)。 - 2、pom文件中申明顺序优先
如果A-B-X(1.0) ,A-C-X(2.0) 这样的路径长度一样怎么办呢?这样的情况下,maven会根据pom文件声明的顺序加载,如果先声明了B,后声明了C,那就最后的依赖就会是X(1.0)。 - 3、覆写优先
子pom内声明的优先于父pom中的依赖。
maven的解决冲突
- 1、通过们mvn dependency:tree查看依赖树,通过maven的依赖原则来调整坐标在pom文件的申明顺序是最好的办法。
- 2、通过exclude排除一些间接依赖,或者把依赖的jar包调整到最前面,按照maven加载顺序最前面加载了jar包后间接引用就不会生效了。
classpath路径先后问题
Of particular importance, and much consternation,
the class loader will load classes in the order they appear in the classpath.
Starting with the first classpath entry,
the class loader visits each specified directory or archive file attempting to find the class to load.
The first class it finds with the proper name is loaded, and any remaining classpath entries are ignored.
说明:
- 上面说明了,java xx -classpath ".;a.jar;b.jar;" 时,如果a.jar和b.jar有重名的类,那么会以a.jar的为准,忽略b.jar的,因为jvm按照-classpath参数的路径先后顺序去load类,后续加载的同名的类会被忽略。
案例分享
- hadoop集群上跑MR任务(自行打的jar包,jar包中依赖的protobuf-java版本高于hadoop集群自身lib目录当中的protobuf-java版本),导致每次先加载低版本的protobuf-java版本然后报方法找不到错误。
- 实际当中针对上面的问题,会在把高版本的jar包放在当前目录并在代码当中把当前目录添加到classpath的最前面,保证先加载高版本的jar包。
tomcat无法加载jar包
真实案例
错误信息
java.lang.NoClassDefFoundError: com/beibei/ai/base/UserModel
catalina.log日志内容
Apr 5, 2013 1:38:26 PM org.apache.catalina.loader.WebappClassLoader validateJarFile
INFO: validateJarFile(/home/frodo/apache-tomcat-7.0.37/webapps/hive/WEB-INF/lib/base-0.0.42-SNAPSHOT.jar) - jar not loaded.
See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class
说明:
- 查看/logs/catalina.log定位到具体的日志。
- 和tomcat当中加载的类javax.servlet.Servlet发生冲突导致类无法加载。
- Stack Overflow问题