Steps:
./build.sh
uv run --with jpype1 python ./demo.py
Output:
ConfigV1 initial: 1
ConfigV2 initial: 2
After changes:
ConfigV1 counter: 100
ConfigV2 counter: 200
| # 1. Compile LoaderManager | |
| mkdir -p my_class_loader | |
| cp LoaderManager.java my_class_loader/ | |
| javac my_class_loader/LoaderManager.java | |
| jar cf my_class_loader.jar my_class_loader/LoaderManager.class | |
| rm -rf my_class_loader | |
| # 2. Compile version 1 | |
| mkdir -p v1/com/example | |
| cp Config-v1.java v1/com/example/Config.java | |
| javac -d v1 v1/com/example/Config.java | |
| jar cf config-v1.jar -C v1 com | |
| rm -rf v1 | |
| # 3. Compile version 2 | |
| mkdir -p v2/com/example | |
| cp Config-v2.java v2/com/example/Config.java | |
| javac -d v2 v2/com/example/Config.java | |
| jar cf config-v2.jar -C v2 com | |
| rm -rf v2 | |
| package com.example; | |
| public class Config { | |
| private static int counter = 1; | |
| public static int getCounter() { | |
| return counter; | |
| } | |
| public static void setCounter(int v) { | |
| counter = v; | |
| } | |
| } |
| package com.example; | |
| public class Config { | |
| private static int counter = 2; | |
| public static int getCounter() { | |
| return counter; | |
| } | |
| public static void setCounter(int v) { | |
| counter = v; | |
| } | |
| } |
| import jpype | |
| import os | |
| # --- Paths --- | |
| base = os.path.dirname(os.path.abspath(__file__)) | |
| loader_jar = os.path.join(base, "my_class_loader.jar") | |
| v1_classpath = [os.path.join(base, "config-v1.jar")] | |
| v2_classpath = [os.path.join(base, "config-v2.jar")] | |
| # --- Start JVM with only the helper JAR --- | |
| jpype.startJVM(classpath=[loader_jar]) | |
| # --- Load the Java helper class --- | |
| LoaderManager = jpype.JClass("my_class_loader.LoaderManager") | |
| # --- Load each version of Config in isolation --- | |
| ConfigV1_Class = LoaderManager.loadClassPath(v1_classpath, "com.example.Config") | |
| ConfigV2_Class = LoaderManager.loadClassPath(v2_classpath, "com.example.Config") | |
| # Wrap the returned Class<?> into a JPype-accessible class type | |
| ConfigV1 = jpype.JClass(ConfigV1_Class) | |
| ConfigV2 = jpype.JClass(ConfigV2_Class) | |
| # --- Use them like normal Java classes --- | |
| print("ConfigV1 initial:", ConfigV1.getCounter()) # 1 | |
| print("ConfigV2 initial:", ConfigV2.getCounter()) # 2 | |
| # Change values independently | |
| ConfigV1.setCounter(100) | |
| ConfigV2.setCounter(200) | |
| print("After changes:") | |
| print(" ConfigV1 counter:", ConfigV1.getCounter()) # 100 | |
| print(" ConfigV2 counter:", ConfigV2.getCounter()) # 200 | |
| jpype.shutdownJVM() |
| package my_class_loader; | |
| import java.net.URL; | |
| import java.net.URLClassLoader; | |
| import java.io.File; | |
| import java.util.*; | |
| import java.util.stream.Collectors; | |
| /** | |
| * A loader that can create isolated class loaders with full classpaths. | |
| */ | |
| public class LoaderManager { | |
| // Cache to reuse loaders if desired (optional) | |
| private static final Map<String, URLClassLoader> loaders = new HashMap<>(); | |
| /** | |
| * Load a class from an isolated class loader given a classpath (list of JARs/dirs). | |
| * | |
| * @param classPathList List of paths (JAR files or directories) | |
| * @param className Fully-qualified class name | |
| */ | |
| public static Class<?> loadClassPath(String[] classPathList, String className) throws Exception { | |
| // Normalize and join the classpath into a cache key | |
| String key = Arrays.stream(classPathList) | |
| .map(File::new) | |
| .map(File::getAbsolutePath) | |
| .sorted() | |
| .collect(Collectors.joining(File.pathSeparator)); | |
| URLClassLoader loader = loaders.computeIfAbsent(key, k -> { | |
| try { | |
| URL[] urls = Arrays.stream(classPathList) | |
| .map(File::new) | |
| .map(f -> { | |
| try { | |
| return f.toURI().toURL(); | |
| } catch (Exception e) { | |
| throw new RuntimeException(e); | |
| } | |
| }) | |
| .toArray(URL[]::new); | |
| return new URLClassLoader(urls, null); // null parent = isolated | |
| } catch (Exception e) { | |
| throw new RuntimeException(e); | |
| } | |
| }); | |
| return loader.loadClass(className); | |
| } | |
| } |