0

I want to select different implementations of classes dynamically, based on a runtime condition. Let's say I have a class with fully qualified class name C. My running system may have many definitions of class C, each of which is in its own jar. I have a runtime condition (held in a ThreadLocal) that tells which definition should be chosen.

I was asked in a comment to clarify the original requirement, so I will clarify the requirement as best I can. There are multiple teams writing software to contribute to this system - something like 4000 classes in many independent modules. What's more they can change over time. They are currently running in separate JVMs so there is no issue with classes overlapping. Now we are considering running them in the same JVM with multiple releases running simultaneously on the same JVM; which specific set of implementations used being differentiated by the ThreadLocal. So the original problem was how to allow a thread to at one time run one set of implementations and at another time run another.

I have a tomcat application that is currently using OpenJDK 8.

I believe I can write a custom ClassLoader that manipulates the classpath to choose the definition of C differently based on the ThreadLocal. But I'm afraid the results will be cached somewhere such as JVM Code Cache. Unless I can override that behavior too, the next time the class is needed, the runtime condition may have changed and the version in the cache would be wrong.

Is there any way to do what I need to do?

fool4jesus
  • 2,147
  • 3
  • 23
  • 34
  • 1
    “cache” is not the right word for it. Classes are only linked once. Code and its linkage is not thread dependent. A condition in a thread local variable is a code smell in the first place, but making linkage depending on it does not work at all. And using the terms “custom class loader” and “class path” in the same sentence suggests, that you haven’t understood how class loading works. – Holger Dec 07 '20 at 08:48
  • Perhaps you are right that I don’t understand class loading, but I don’t see how that follows from that sentence. The custom class loader would not be written from whole cloth: it would be a subclass of tomcat’s WebappLoader and its loadClass method would simply manipulate the class path as needed and then call super.loadClass. This may be wrong, but please enlighten me as to why. @Holger – fool4jesus Dec 07 '20 at 12:34
  • 1
    A class path is a global system property. Manipulating it for the sake of a single class loading operation in a single thread would break all other class loading operations that could happen at the same time. And it makes no sense to mess around with global configurations given that a *custom class loader* does not need a class path, as it can read a class file from a jar file and make a class of it with two lines of code. Using the class `URLClassLoader` instead of implementing it yourself provides it even for free. But it doesn’t help the issue that linkage can not be done more than once. – Holger Dec 07 '20 at 12:54
  • Hm, if it's a global property, then I can see how that would be a problem. I wouldn't want to use URLClassLoader because that would be a regression testing nightmare with over 30,000 existing classes in the app. But regardless as you say, it doesn't help the basic problem. – fool4jesus Dec 07 '20 at 15:16
  • 1
    It looks like an [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem/66378#66378). Forget about your idea of conditional class loading and start with the problem you actually wanted to solve with conditional class loading… – Holger Dec 07 '20 at 16:51
  • @Holger Perhaps it is. I thought I stated the original problem, but I will update the question to be more specific. – fool4jesus Dec 07 '20 at 21:24
  • 2
    Now that’s helpful. The JVM will resolve dependencies through the defining loader of a class. So when you create a distinct class loaders for every module that had its own JVM in the old setup, their dependencies will get resolved through their specific loader. Each of those class loaders can have its own version of C. You can optimize it by using the same loader for all classes using the same version of C (if your old setup had more JVMs than distinct versions of C). There’s no need for conditionals, as the JVM does already remember the defining loader. – Holger Dec 08 '20 at 09:48
  • @Holger Please move this to an answer. – fool4jesus Dec 08 '20 at 12:37

2 Answers2

1

Well, the simple solution would be to NOT have multiple definitions of class C, but instead have 'class C1', 'class C2', etc (i.e. they don't overlap and can be simultaneously loaded) and then your runtime property just picks the right one as appropriate. That is way-way the easiest solution, so strongly consider it first. But it may not meet your needs.

If you truly need to have multiple separate implementations of a single 'class C', then what you are effectively talking about is a 'hot swap' scenario. Fortunately, Tomcat and other tools got good (with limitations) at hotswapping long-time-back. You likely already know this, but 'hot swapping' meets the need of a developer who codes 'class C', deploys it to a container, tries it out, realizes it has a glitch, makes a quick code edit and wants to run the modified code without relaunching the container. Hotswapping does this by basically overlaying the new implementation in the JVM. It only works up to a point because each 'redeploy' pollutes the 'class space' of the JVM and you eventually run of of 'class space memory' and/or the JVM starts going unstable. Depending on your needs and tolerances though, hotswapping might work.

Jeff Bennett
  • 996
  • 7
  • 18
  • Hmm... you’ve given me an idea. We already have OSGi (Felix) in the system, but we use it for only specific purposes. Maybe we could expand its use for this. Does that sound right? – fool4jesus Dec 07 '20 at 12:24
  • 1
    Hotswapping only works that way when all classes directly or indirectly linked the old version also get hostwapped. When you manage to correctly stop using the old classes and have no leaks to old instances of those classes, there is no ‘class space’ pollution problem, as the classes get unloaded after their class loader got garbage collected. – Holger Dec 07 '20 at 13:07
1

The clean way to do what you want is to define C as an interface, then you can load and use any class that implements interface C

ddyer
  • 1,792
  • 19
  • 26
  • Thanks. The problem is there are literally hundreds - perhaps thousands - of these classes, with new ones being written all the time, by a widely distributed team. There are also backward compatibility issues since these classes need to work with the same “core” system from two years ago. – fool4jesus Dec 07 '20 at 12:18