【问题标题】:Isolating a static singleton class using class loaders使用类加载器隔离静态单例类
【发布时间】:2018-04-20 06:53:03
【问题描述】:

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

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

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

【问题讨论】:

  • 你必须在它们自己的类加载器中隔离/复制整个类层次结构,在你走下去之前,有很多更容易查看的可能性。但是为了让我们考虑这些可能性,您必须解释该类的作用、它的使用方式以及您的根本问题是什么(而不是您如何考虑解决它)。
  • @Kayaman 问题的根源在于遗留代码库没有提供注入配置的好方法。由于单例模式,它们与类紧密相关。如果不对代码库进行重大重写,就没有真正的方法可以为许多类提供多种配置。这可能不是最好的解决方案,但我很好奇这个解决方案需要什么(作为一个教育练习)。实际上,我最好的选择可能是将应用程序/环境容器化并运行应用程序的多个实例。
  • 动态加载单例类是不够的。您必须加载层次结构,这将导致容器化之类的东西。但这可能会采取艰难的方式。不知道具体情况很难说。
  • @Kayaman 我明白了;这就说得通了。我很好奇的一个优势是无需启动多个 JVM 实例可以节省程序内存。但是如果我必须重新加载类路径中的所有内容,那么我可能不会节省太多任何东西,最终会大大增加应用程序的复杂性。那就是说;我仍然很好奇这个实现会是什么样子。我找不到任何可以使用的示例,但我可能只是不知道要搜索的正确流行语。
  • 您需要向我们展示您如何决定流程显示采用路径 A 或 B,以及如何处理请求,使用什么框架?

标签: java static classloader legacy


【解决方案1】:

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

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

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

灵感来自post

  • 隔离代码库,主要是具有LegacyRequestHandler 类的 jar,并且不要包含在应用程序/主类路径中。
  • 有一个自定义类加载器,它首先向内查看,然后检查父级。这种类加载器的完整示例是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】:

    /首先,告诉你的领导你要花时间制作使用更多内存的复杂代码,并且由于 jit 重新编译和其他类初始化问题可能会更慢,因为你不被允许修复糟糕的过时代码。时间和可维护性就是金钱。/

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

    另一个激进的解决方案是以调用者在调用处理程序之前可以设置的方式(使用属性文件或使用 Threadlocal 字段(假设工作在同一个线程上完成)重新实现有问题的类,这反过来也不会看到假面舞会)。

    您必须在类路径中优先部署覆盖类(之前列出 jar),或者对于 web 应用程序,如果可以,部署在覆盖 web-inf/lib 中任何内容的 web-inf/classes 中.您最终可以从遗留 jar 中删除该类。关键是要有相同的类名、相同的方法签名,但要有一个新的实现(同样,这依赖于加载 cfg 文件或在调用之前使用线程本地设置)。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

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

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

      在这种情况下,最好知道为什么您不能更改具有明显且非常严重的设计问题的软件。因为这是真正的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-26
        • 2011-08-30
        • 1970-01-01
        • 1970-01-01
        • 2012-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多