使用类加载器隔离静态单例类

fla*_*kes 8 java legacy static classloader

免责声明:鉴于这个问题,这可能不是最佳解决方案,但我很好奇如何实现这一实施.

问题我正在尝试处理一些遗留代码,这些代码的单例定义如下:

public class LegacySingleton {
    private static Boolean value;

    public static void setup(boolean v) {
        if (value != null) {
            throw new RuntimeException("Already Set up");
        }
        value = v;
        System.out.println("Setup complete");
    }

    public static void teardown() {
        value = null;
        System.out.println("Teardown complete");
    }

    public static boolean getValue() {
        return value;
    }
}
Run Code Online (Sandbox Code Playgroud)

我无法更改此设计,并且在整个代码库中大量使用该类.此单例返回的值可以极大地改变代码的功能.例如:

public class LegacyRequestHandler {
    public void handleRequest() {
        if (LegacySingleton.getValue()) {
            System.out.println("Path A");
        } else {
            System.out.println("Path B");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我想要代码Path A,那么我必须以LegacySingleton特定的方式初始化.如果我想接受Path B我必须重新初始化LegacySingleton.无法并行处理采用不同路径的请求; 对于每个不同的配置,LegacySingleton我需要启动一个单独的JVM实例.


我的问题是否有可能使用单独的类加载器来隔离这个单例?我一直在玩ClassLoaderAPI,但我无法弄明白.

我想它会看起来像这样:

public class LegacyRequestHandlerProvider extends Supplier<LegacyRequestHandler> {
    private final boolean value;
    public LegacyRequestHandlerProvider(boolean value) {
        this.value = value;
    }
    @Override
    public LegacyRequestHandler get() {
        LegacySingleton.setup(value);
        return new LegacyRequestHandler();
    }
}
Run Code Online (Sandbox Code Playgroud)

...

ClassLoader loader1 = new SomeFunkyClassLoaderMagic();
Supplier<LegacyRequestHandler> supplier1 = loader1
    .loadClass("com.project.LegacyRequestHandlerProvider")
    .getConstructor(Boolean.TYPE)
    .newInstance(true);

ClassLoader loader2 = new SomeFunkyClassLoaderMagic();
Supplier<LegacyRequestHandler> supplier2 = loader2
    .loadClass("com.project.LegacyRequestHandlerProvider")
    .getConstructor(Boolean.TYPE)
    .newInstance(false);

LegacyRequestHandler handler1 = supplier1.get();
LegacyRequestHandler handler2 = supplier2.get();
Run Code Online (Sandbox Code Playgroud)

suj*_*jit 3

简单的反射是否在您希望输出发生变化的特定时间点起作用getValue

Field f = LegacySingleton.class.getDeclaredField("value");
f.setAccessible(true);
f.set(null, true|false);
Run Code Online (Sandbox Code Playgroud)

如果没有,对于类加载器方法,您可以遵循插件架构。但正如其他人所指出的,这可能归结为将整个依赖项加载到两个不同的类加载器层次结构上。此外,您可能会遇到LinkageError问题,具体取决于依赖项在代码库中的工作方式。

受到这篇文章的启发:

  • 隔离代码库,主要是具有类的 jar LegacyRequestHandler,并且不包含在应用程序/主类路径中。
  • 有一个自定义类加载器,首先向内查找,然后检查父类。此类类加载器的完整示例位于此处
  • 有一个包装器调用程序,它将使用提供类的 jar 路径初始化类加载器LegacySingleton,例如

    new ParentLastURLClassLoader(Arrays.asList(new URL[] {new URL("path/to/jar")}));

  • 之后,您可以将单例加载到其类加载器空间中并获取副本。


    //2 different classloaders 
    ClassLoader cl1 = new ParentLastURLClassLoader(urls);
    ClassLoader cl2 = new ParentLastURLClassLoader(urls);
    //LegacySingleton with value = true in Classloader space of cl1
    cl1.loadClass("LegacySingleton").getMethod("setup", boolean.class).invoke(null, true);
    //LegacySingleton with value = false in Classloader space of cl1
    cl2.loadClass("LegacySingleton").getMethod("setup", boolean.class).invoke(null, false);
Run Code Online (Sandbox Code Playgroud)
  • 接下来,您可以使用反射(通过)和触发器执行来获取遗留代码的驱动程序类cl1/2

请注意,您不应直接在主类中引用旧代码中的类,因为这样它们将使用 Java 原始/应用程序类加载器进行加载。