(旧文整理)彻底搞懂Java ClassLoader(六)
之前的文章里,我们学习了::Classloader::的基本使用方法及其对Class的::加载方式::,并且动手做了一个ClassLoader,在本文中,我们将学习ClassLoader的::命名空间::。
ClassLoader的命名空间(Namespace)
在之前的文章里,我们看到数组类在JVM中的名字和我们在编码中使用的不一样。其实JVM还向我们隐藏了类加载的::另一个细节:::
在JVM当中,ClassLoader会被当成命名空间来分隔各自加载的类
这样就会产生两个结果:
- 项目中可以使用::不同的ClassLoader::来加载包名、类名完全相同的Class,但是这两个Class的内容可以完全不一样。
- 即使是同一个Class文件,如果被::两个不同的ClassLoader来加载::,那么在JVM中它们相互独立,::不算是同一个Class::。
第一种情况我们可能会经常遇到,比如使用开源框架时,有的框架里面可以看到对同一个项目有多个不同版本的依赖,有的地方使用::版本x::,有的地方使用::版本y::,x与y的包名,类名都一样,但里面的内容可以已经不同,却可以相安无事。这就是由于它们运行在各自独立的ClassLoader当中。
对于第二种情况,本质上与第一种,因为只要是::不同的ClassLoader加载的类::,就被认为是各自独立的。
为了直观地看到这个效果,我们可以分别创建两个在上一篇文章中制作的SimpleClassloader
,让它们读入::同一个class::。下面是代码:
package io.alchemystudio.classloader.demo.namespaces;
import io.alchemystudio.classloader.SimpleClassLoader2;
import io.alchemystudio.classloader.demo.impl.ProductImpl;
public class TwoLoaders {
public static String getClazzPath(Class clazz) {
String clazzToPath = clazz.getName().replaceAll("\\.", "/") + ".class";
return "target/classes/" + clazzToPath;
}
public static void main(String[] args) throws Exception {
String clazzPath = getClazzPath(ProductImpl.class);
ClassLoader loaderX = new SimpleClassLoader2(clazzPath);
ClassLoader loaderY = new SimpleClassLoader2(clazzPath);
Class clazzX = loaderX.loadClass(ProductImpl.class.getName());
Class clazzY = loaderY.loadClass(ProductImpl.class.getName());
System.out.println("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
System.out.println("X: " + clazzX.getName());;
System.out.println("Y: " + clazzY.getName());;
System.out.println("X = Y? " + (clazzX == clazzY));
}
}
可以看到我们创建了两个SimpleClassLoader2
的实利,然后分别用它们加载ProductImpl
的class,得到clazzX
和clazzY
。最后比较这两个加载的classes是否等价。运行上面的代码得到结果如下:
可以看到最后的结果:虽然X
和Y
都是对应ProductImpl
这个class,但是它们俩并不等价。
这是因为,这两个classes是用不同的::class loader::进行加载的。我们可以进一步使用加载出来的class:
Object x = clazzX.getDeclaredConstructor().newInstance();
System.out.println(x.getClass());
如上所示,我们用加载出来的classX
来生成一个实例x
,并查看它的class,运行结果如下:
可以看到x
的class为ProductImpl
,那么我们cast一下这个object:
ProductImpl y = (ProductImpl) x;
运行上面的代码会得到什么结果呢?实际结果如下:
可以看到发生了ClassCastException
,并且仔细看exception message的话,输出如下:
io.alchemystudio.classloader.demo.impl.ProductImpl incompatible with io.alchemystudio.classloader.demo.impl.ProductImpl
这是为什么呢?
道理是一样的:因为ProductImpl y
这里的ProductImpl
,这个class是java当前的main()
方法的thread的::class loader::加载的,而classX
是我们自己的loaderX
加载出来的,所以虽然对应一个::class文件::,但是它实际上::不在一个namespace里::面,所以不可以cast。
因此,如果我们要使用这样的特定的class loader加载出来的class,就要用反射的方法调用和生成实例。这一篇文章讲了class loader的namespace,下一篇讲一下class的多种加载方式。本文用到的代码在这里:
- 上一篇 k3s学习笔记(一)
- 下一篇 k3s学习笔记(二)