(旧文整理)彻底搞懂Java ClassLoader(六)

之前的文章里,我们学习了::Classloader::的基本使用方法及其对Class的::加载方式::,并且动手做了一个ClassLoader,在本文中,我们将学习ClassLoader的::命名空间::。

ClassLoader的命名空间(Namespace)

在之前的文章里,我们看到数组类在JVM中的名字和我们在编码中使用的不一样。其实JVM还向我们隐藏了类加载的::另一个细节:::

在JVM当中,ClassLoader会被当成命名空间来分隔各自加载的类

这样就会产生两个结果:

第一种情况我们可能会经常遇到,比如使用开源框架时,有的框架里面可以看到对同一个项目有多个不同版本的依赖,有的地方使用::版本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,得到clazzXclazzY。最后比较这两个加载的classes是否等价。运行上面的代码得到结果如下:

可以看到最后的结果:虽然XY都是对应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的多种加载方式。本文用到的代码在这里:

Powered by Jekyll and Theme by solid