简体   繁体   English

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

[英]Isolating a static singleton class using class loaders

Disclaimer: This is probably not the best solution given the issue, but I'm curious how this implementation could be achieved. 免责声明:鉴于此问题,这可能不是最佳解决方案,但我很好奇如何实现这一实施。

Problem I'm trying to deal with some legacy code which has a singleton defined like bellow: 问题我正在尝试处理一些遗留代码,这些代码的单例定义如下:

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

I do not have the ability to change this design and the class is used heavily throughout the code base. 我无法更改此设计,并且在整个代码库中大量使用该类。 The values returned by this singleton can greatly change the functionality of the code. 此单例返回的值可以极大地改变代码的功能。 Eg: 例如:

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

Right now if I want the code to take Path A , then I have to initialize LegacySingleton in a particular way. 现在,如果我希望代码采用Path A ,那么我必须以特定方式初始化LegacySingleton If I then want to take Path B I have to re-initialize the LegacySingleton . 如果我想要使用Path B我必须重新初始化LegacySingleton There is no way of handling requests in parallel which take different paths; 无法并行处理采用不同路径的请求; meaning for each different configuration of LegacySingleton required I need to launch a separate JVM instance. 对于LegacySingleton每个不同配置的LegacySingleton ,我需要启动一个单独的JVM实例。


My Question Is it possible to isolate this singleton using separate class loaders? 我的问题是否有可能使用单独的类加载器来隔离这个单例? I've been playing around with the ClassLoader API, but I cant quite figure it out. 我一直在使用ClassLoader API,但我无法弄明白。

I'm imagining it would look something along the lines of this: 我想它会看起来像这样:

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

/ First, tell your lead that you are about spend time and make convoluted code that uses more memory and could be slower due to jit re-compilation and other class init issues, because you are somehow not allowed to fix bad obsolete code. / 首先,告诉你的主管你是在花时间制作使用更多内存的复杂代码,并且由于jit重新编译和其他类init问题可能会变慢,因为你不知道如何修复糟糕的过时代码。 Time and maintainability is money. 时间和可维护性就是金钱。 / /

Ok, now... Your funky classloaders are just URL classloaders with the required jars given. 好的,现在......你的时髦的类加载器只是带有所需罐子的URL类加载器。 But the trick is to NOT have the singleton nor the handlers in the main classpath, otherise the classloader will find the class in a parentclassloader (that has precedence) and it will still be a singleton. 但诀窍是在主类路径中没有单例和处理程序,否则类加载器将在parentclassloader(具有优先权)中找到该类,并且它仍然是单例。 You knew that I'm sure. 你知道我很有信心。

Another radical solution is to re-implement the offending class your way (with properties file, or with a Threadlocal field (assuming the work is done on same thread) that a caller can set before making the call to the handler, which in turn will not see the mascarade). 另一个根本的解决方案是以你的方式重新实现违规类(使用属性文件,或者使用Threadlocal字段(假设工作在同一个线程上完成),调用者可以在调用处理程序之前设置它,这反过来将看不到mascarade)。

You would have to deploy your overriding class with precedence in the classpath (list the jar earlier), or for a webapp, if you can, deploy in the web-inf/classes which overrides anything in the web-inf/lib. 您必须在类路径中优先部署覆盖类(在前面列出jar),或者如果可以的话,在web-inf / classes中部署web-inf / lib中的任何内容。 You can ultimately just delete the class from the legacy jar. 您最终可以从旧jar中删除该类。 The point is to have the same classname, same methods signature, but a new implementation (again, that relies on loading cfg files or using threadlocal setup before the call). 重点是具有相同的类名,相同的方法签名,但是一个新的实现(同样,它依赖于加载cfg文件或在调用之前使用threadlocal设置)。

Hope this helps. 希望这可以帮助。

Does plain simple reflection work at specific points in time, where you want the output of getValue to change? 简单的简单反射是否适用于特定时间点,您希望getValue的输出更改?

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

If not, For the Classloader approach, you can follow a Plugin architecture. 如果没有,对于Classloader方法,您可以遵循插件架构。 But as others have noted, this may boil down to the whole dependencies being loaded on 2 different Classloader hierarchies. 但正如其他人所说,这可能归结为在2个不同的Classloader层次结构上加载的整个依赖关系。 Also, you may face LinkageError issues, depending on how the dependencies work in your codebase. 此外,您可能会遇到LinkageError问题,具体取决于依赖项在代码库中的工作方式。

Inspired by this post : 灵感来自这篇文章

  • Isolate the codebase, primarily the jar having LegacyRequestHandler class and do not include in the application/main classpath. 隔离代码库,主要是具有LegacyRequestHandler类的jar,并且不包含在application / main类路径中。
  • Have a custom classloader that looks inwards first and then checks parent. 有一个自定义类加载器,首先向内看,然后检查父类。 A complete example of such a classloader is here . 这样的类加载器的一个完整的例子是在这里
  • Have a wrapper invoker that will initialise the class loader with the jar path providing LegacySingleton class, eg 有一个包装调用程序,它将使用提供LegacySingleton类的jar路径初始化类加载器,例如

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

  • Post that, you can load the singleton in it's class loader space and obtain copies. 发布,您可以在其类加载器空间中加载单例并获取副本。


    //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);
  • Next, you may obtain the driver class for your legacy code using reflection(through cl1/2 ) and trigger execution. 接下来,您可以使用反射(通过cl1/2 )和触发器执行来获取遗留代码的驱动程序类。

NOTE that you shouldn't refer to the classes in the Legacy code directly in the main class, as then they will be loaded using Java primordial/application class loader. 请注意 ,您不应直接在主类中引用Legacy代码中的类,因为它们将使用Java原始/应用程序类加载器加载。

In my view –and my apologies that this is a largely opinion-based answer– this is a business problem and not a technical problem, because the constraints given ("I cannot change the code") are not technical ones. 在我看来 - 我很抱歉这是一个基于意见的答案 - 这是一个业务问题而不是技术问题,因为给出的限制(“我不能改变代码”)不是技术问题。 But as anybody working in software development can attest, business constraints are part and parcel of our jobs. 但正如任何从事软件开发工作的人都可以证明的那样,业务限制是我们工作的重要组成部分。

Your issue can be abstracted as follows: "Considering constraint A, can I get result B?" 您的问题可以抽象如下:“考虑约束A,我可以得到结果B吗?” And the answer is: "No, you cannot." 答案是:“不,你不能。” Or perhaps you can, but with a solution that is difficult –in other words, expensive– to maintain and prone to break. 或者也许你可以,但是用一个难以解决的解决方案 - 换句话说,昂贵 - 维护并且容易中断。

In cases like these, it would be good to know why you cannot change the software that has obvious and very serious design problems. 在这些情况下,最好知道为什么你不能改变那些有明显和非常严重的设计问题的软件。 Because that is the real problem. 因为那是真正的问题。

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

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