项目存在两个有相同类的jar包会发生什么
下面是两个jar包的代码,有相同包名和相同类名的两个类,如下:
把两个文件打包成两个jar包引入同一个项目中,调用。
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestUse t = new TestUse();
t.say();
}
}
执行结果如下:
这是第一个jar包
这样的原因
因为jar包中的类是使用AppClassLoader加载的(如下图),而类加载器中有一个命名空间的概念,同一个类加载器下,相同包名和类名的class只会被加载一次,如果已经加载过了,直接使用加载过的。所以导致下面的执行结果,下面的执行结果是不确定的。不会报错,也不会编译失败。
怎么执行指定jar包下的类文件
这里需要使用到自定义类加载器,所以请大家先简单了解一下自定义类加载器请点击这里查看之前的文章,此处的类加载器也是基于做的简单修改。
private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
name = name.replace(".", "\\");
// 如果path是jar包的路径,那么上述方法不能加载成功,抛出异常后执行此处的方法。
JarFile jar = new JarFile(this.path);
is = jar.getInputStream(jar.getEntry(name.replace("\\", "/") + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
return data;
}
基于之前的类加载器加载,只是简单的修改了下面的代码,也就是在jar包中读取字节流。
JarFile jar = new JarFile(this.path);
is = jar.getInputStream(jar.getEntry(name.replace("\\", "/") + this.fileExtension));
怎么使用自定义加载类去分别执行
package com.xu.test;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import com.xu.classloader.MyClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
// 获取所有TestUse得到路径
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> urls = classloader.getResources("com/wenbin/TestUse.class");
// 循环加载两个jar包下的
while (urls.hasMoreElements()) {
URL url = (URL) urls.nextElement();
String fullPath = url.getPath();
String[] strs = fullPath.split("!");
// 获取文件的路径以及文件名
String path = strs[0].replace("file:/","");
String fileName = strs[1].substring(1).replace(".class", "").replace("/", ".");
MyClassLoader loader = new MyClassLoader(null,"loader");
loader.setPath(path);
// 加载类
Class<?> clazz = loader.loadClass(fileName);
// 反射创建实体
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("say", null);
// 执行
method.invoke(obj, null);
}
}
}
运行结果
这是第一个jar包
这是第二个jar包
此处最重要的下面的代码,也就是把类加载器的夫类加载器指定为null,也就是根类加载器,而根类加载器无法加载jar包中的类文件,所以就由自定义的类加载器进行加载,由于两个类加载器是兄弟类加载器,所以是彼此不可见的,也就是分别加载两个类。
MyClassLoader loader = new MyClassLoader(null,"loader");
写到最后
类似于这种的问题其实不是特别难以解决,但是如果不熟悉类加载器的相关知识,就对这种问题完全没有思路,所以在学习JVM的时候,了解思想很重要,在平时可能使用的很少,但是面对棘手的问题时就很容易解决。