繁体   English   中英

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

[英]Isolating a static singleton class using class loaders

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

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

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每个不同配置的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();

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

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

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

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

希望这可以帮助。

简单的简单反射是否适用于特定时间点,您希望getValue的输出更改?

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

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

灵感来自这篇文章

  • 隔离代码库,主要是具有LegacyRequestHandler类的jar,并且不包含在application / main类路径中。
  • 有一个自定义类加载器,首先向内看,然后检查父类。 这样的类加载器的一个完整的例子是在这里
  • 有一个包装调用程序,它将使用提供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原始/应用程序类加载器加载。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM