不是单例的单例——巧用ClassLoader

本文通过一个特殊场景探讨如何在Java中实例化两个单例类,引出类加载器的概念和工作机制,包括反射、双亲委派模型和自定义类加载器的使用。通过自定义类加载器,实现了在同一JVM进程中创建两个隔离的单例对象,避免了反射带来的部分隔离问题。
摘要由CSDN通过智能技术生成

本文通过如何将一个单例类实例化两次的案例,用代码实践来引入 Java 类加载器相关的概念与工作机制。理解并熟练掌握相关知识之后可以扩宽解决问题的思路,另辟蹊径,达到目的。

背景

单例模式是最常用的设计模式之一。其目的是保证一个类在进程中仅有一个实例,并提供一个它的全局访问方式。那什么场景下一个进程里需要单例类的两个对象呢?很明显这破坏了单例模式的设计初衷。

这里举例一个我司的特殊场景:

RPC 的调用规范是每个业务集群里只能有一个调用方,如果一个业务节点已经实例化了一个客户端,就无法再实例化另一个。这个规范的目的是让一个集群统一个调用方,方便服务数据的收集、展示、告警等操作。

一个项目有多个集群,多个项目组维护,各个集群都有一个共同特点,需要调用相同的 RPC 服务。如果严格按照上述 RPC 规范的话,每一个集群都需要申请一个自己调用方,每一个调用方都申请相同的 RPC 服务。这样做完全没有问题,只是相同的工作会被各个集群都做一遍,并且生成了多个 RPC 的调用方。

最终方案是将相同的逻辑代码打包成一个公用 jar 包,然后其他集群引入这个包就能解决我们上述的问题。这么做的话就碰到了 RPC 规范中的约束问题,jar 包里的公用逻辑会调用 RPC 服务,那么势必会有一个 RPC 的公用调用方。我们的业务代码里也会有自己业务需要调用的其他 RPC 服务,这个调用方和 jar 包里的调用方就冲突了,只能有一个调用方会被成功初始化,另一个则会报错。这个场景是不是就要实例化两个单例模式的对象呢。

有相关经验的读者可能会想到,能不能把各个集群中相同的工作抽取出来,做成一个类似网关的集群,然后各个集群再来调用这个公用集群,这样同一个工作也不会被做多遍,RPC 的调用方也被整合成了一个。这个方案也是很好的,考虑到一些客观因素,最终并没有选择这种方式。

实例化两个单例类

我们假设下述单例类代码是 RPC 的调用 Client:

public class RPCClient {
   
  	private static BaseClient baseClient;
    private volatile static RPCClient instance;
  
  	static {
   
        baseClient = BaseClient.getBaseClient();
    }
  
    private RPCClient() {
   
       System.out.println("构造 Client");
    }
    public String callRpc() {
   
        return "callRpc success";
    }
    public static RPCClient getClient() {
   
        if (instance == null) {
   
            synchronized (RPCClient.class) {
   
                if (instance == null) {
   
                    instance = new RPCClient();
                }
            }
        }
        return instance;
    }
}
public class BaseClient {
   
  ...
  private BaseClient() {
   
      System.out.println("构造 BaseClient");
  }
  ...
}

这个单例 Client 有一点点不同,就是有一个静态属性 baseClient,BaseClient 也是一个简单的单例类,构造方法里有一些打印操作,方便后续观察。baseClient 属性通过静态代码块来赋值。

我们可以想一想,有什么办法可以将这个单例的 Client 类实例化两个对象出来?

无所不能的反射大法

最容易想到的就是利用反射获取构造方法,来规避单例类私有化构造方法的约束来实例化:

Constructor<?> declaredConstructor = RPCClient.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object rpcClient = declaredConstructor.newInstance();
Method sayHi = rpcClient.getClass().getMethod("callRpc");
Object invoke = sayHi.invoke(rpcClient
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值