设为首页 加入收藏

TOP

不是单例的单例——巧用ClassLoader(二)
2023-07-25 21:25:11 】 浏览:55
Tags:单例的 单例 巧用 ClassLoader
sStream(fileName); //如果查找不到文件 则委托父类加载器实现 这里的父加载器就是 AppClassLoader if (resourceAsStream == null) { return super.loadClass(name); } //读取文件 并加载类 byte[] bytes = new byte[resourceAsStream.available()]; resourceAsStream.read(bytes); return defineClass(name, bytes, 0, bytes.length); } }

测试代码如下:

//实例化自定义类加载器
MyClassLoader myClassLoader = new MyClassLoader();
//获取当前线程的 ContextClassLoader 备用
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//设置当前线程的 ContextClassLoader 为实例化的自定义类加载器(这么做的原因下文会补充)
Thread.currentThread().setContextClassLoader(myClassLoader);
//通过自定义类加载器加载 RPCClient
Class<?> rpcClientCls = myClassLoader.loadClass("com.ppphuang.demo.classloader.single.RPCClient");
//将当前线程的 ContextClassLoader 还原为初始的 contextClassLoader
Thread.currentThread().setContextClassLoader(contextClassLoader);
//通过反射获取该类的 getClient 方法
Method getInstance = rpcClientCls.getMethod("getClient");
getInstance.setAccessible(true);
//调用 getClient 方法获取单例对象
Object rpcClient = getInstance.invoke(rpcClientCls);
//获取 callRpc 方法
Method callRpc = rpcClientCls.getMethod("callRpc");
//调用 callRpc 方法
Object callRpcMsg = callRpc.invoke(rpcClient);
System.out.println(callRpcMsg);
//执行输出
//构造 BaseClient
//构造 Client
//callBaseRpc successcallRpc success

通过测试代码的输出可以看到,RPCClient BaseClient 这两个类构造方法里的打印都输出了,那就说明通过自定义类加载器实例化的两个对象都执行了构造方法。自然就跟直接调用 RPCClient.getClient() 生成的对象是完全隔离开的。

你可以通过代码注释,来理解一下测试代码的执行过程。

如果看到这里你还有一些疑问的话,我们再巩固一下类加载器相关的知识。

类与类加载器

默认类加载

在 Java 中有三个默认的类加载器:

BootstrapClassLoader

加载 Java 核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)。用于提供 JVM 自身需要的类。由 C++ 加载,用如下代码去获取的话会显示为 null:

System.out.println(String.class.getClassLoader());
ExtClassLoader

Java 语言编写,从 java.ext.dirs 系统属性所指定的目录中加载类,或从 JDK 的安装目录 jre/lib/ext 子目录下加载类。如果用户创建 的 jar 放在此目录下,也会自动由 ExtClassLoader 加载。

System.out.println(com.sun.crypto.provider.DESedeKeyFactory.class.getClassLoader());
AppClassLoader

它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类,应用程序中默认是系统类加载器。

System.out.println(ClassLoader.getSystemClassLoader());

如果我们没有特殊指定类加载器的话,JVM 进程中所有需要的类都会由上述三个类加载来完成加载。

每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的:

class Class<T> {
  private final ClassLoader classLoader;
}

你可以这样来获取某个类的 ClassLoader:

System.out.println(obj.getClass().getClassLoader());

不同类加载器的影响

两个类相同的前提是类的加载器也相同,不同类加载器加载同一个 Class 也是不一样的 Class,会影响 equals、instanceof 的运算结果。

下面的代码展示了不同类加载器对类判等的影响,为了减少代码篇幅,代码省略了异常处理:

public class ClassLoaderTest {
    public static void main(String[] args) {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) {
                Class<?> loadedClass = findLoadedClass(name);
                if (loadedClass != null) return loadedClass;
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream resourceAsStream = getClass().getResourceAsStream(fileName);
                if (resourceAsStream == null) {
                    return super.loadClass(name);
                }
                byte[] bytes = new byte[resourceAsStream.available()];
                resourceAsStream.read(bytes);
                return defineClass(name, bytes, 0, bytes.length);
            }
        };
        Object obj = myClassLoader.loadClass("ClassLoaderTest").newInstance();
首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Springboot通过谷歌Kaptcha 组件.. 下一篇SpringBoot 使用 Sa-Token 完成注..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目