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

时间:2018-04-20 06:53:04

标签: java static classloader legacy

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

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

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;
    }
}

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

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

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

我的问题是否可以使用单独的类加载器隔离此单例?我一直在使用ClassLoader API,但我无法弄明白。

我想象它会看起来像这样:

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();
    }
}

...

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();

3 个答案:

答案 0 :(得分:3)

/ 首先,请告诉您的主管您是否花时间制作使用更多内存的复杂代码,并且由于jit重新编译和其他类初始化问题而可能会变慢,因为您不知道如何修复糟糕的过时代码。时间和可维护性就是金钱。 /

好的,现在......你的时髦的类加载器只是带有所需jar的URL类加载器。但诀窍是在主类路径中没有单例和处理程序,否则类加载器将在parentclassloader(具有优先权)中找到该类,并且它仍然是单例。你知道我确定。

另一个根本的解决方案是以你的方式重新实现违规类(使用属性文件,或者使用Threadlocal字段(假设工作在同一个线程上完成),调用者可以在调用处理程序之前设置该字段,反过来不会看到mascarade)。

你必须在类路径中优先部署覆盖类(在前面列出jar),或者如果可以的话,在web-inf / classes中部署覆盖web-inf / lib中任何内容的webapp 。您最终可以从旧jar中删除该类。重点是具有相同的类名,相同的方法签名,但是一个新的实现(再次,它依赖于在调用之前加载cfg文件或使用threadlocal设置)。

希望这有帮助。

答案 1 :(得分:3)

简单的简单反射是否适用于特定时间点,您希望getValue的输出发生变化?

Field f = LegacySingleton.class.getDeclaredField("value");
f.setAccessible(true);
f.set(null, true|false);

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

受此post启发:

  • 隔离代码库,主要是具有LegacyRequestHandler类的jar,并且不包含在application / main类路径中。
  • 拥有一个首先向内看的自定义类加载器,然后检查父级。这种类加载器的完整示例是here
  • 有一个包装器调用器,它将使用提供LegacySingleton类的jar路径初始化类加载器,例如

    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);
  • 接下来,您可以使用反射(通过cl1/2)获取遗留代码的驱动程序类并触发执行。

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

答案 2 :(得分:2)

在我看来 - 我很抱歉这是一个基于意见的答案 - 这是一个业务问题而不是技术问题,因为给出的限制(&#34;我不能改变代码&#34;)是不是技术问题。但正如任何从事软件开发工作的人都可以证明的那样,业务限制是我们工作的重要组成部分。

你的问题可以抽象如下:&#34;考虑到约束A,我可以得到结果B吗?&#34;答案是:&#34;不,你不能。&#34;或者也许你可以,但是用一个难以解决的解决方案 - 换句话说,昂贵 - 维护并且容易中断。

在这些情况下,最好知道为什么你不能改变那些有明显和非常严重的设计问题的软件。因为那是真正的问题。