Isolated Class Loaders
Use isolated class loaders to prevent dependency conflicts.
Isolated class loaders provide complete dependency isolation, preventing conflicts between different versions of the same library by loading them in separate class loading contexts.
Why Use Isolated Class Loaders?
Isolated class loaders solve several problems:
- Version conflicts: Load different versions of the same library
- Namespace pollution: Keep main classpath clean
- Plugin isolation: Separate plugin dependencies from each other
- Runtime flexibility: Load and unload dependencies as needed
Creating Isolated Class Loaders
Named Isolated Class Loaders
// Create a named isolated class loader
IsolatedClassLoader isolatedLoader = libraryManager.createNamedIsolatedClassLoader("my-feature");
// Load dependencies into it
List<Dependency> dependencies = List.of(
Dependency.of("com.google.code.gson", "gson", "2.10.1"),
Dependency.of("org.yaml", "snakeyaml", "2.0")
);
libraryManager.loadDependencies(isolatedLoader, dependencies);
// Use classes from the isolated loader
Class<?> gsonClass = isolatedLoader.loadClass("com.google.gson.Gson");
Object gson = gsonClass.getDeclaredConstructor().newInstance();
// Don't forget to close when done
isolatedLoader.close();
Global Isolated Class Loader
// Get the shared global isolated class loader
IsolatedClassLoader globalLoader = libraryManager.getGlobalIsolatedClassLoader();
// Load dependencies into global isolated loader
libraryManager.loadDependencies(globalLoader, dependencies);
// Access classes (no need to close global loader)
Class<?> yamlClass = globalLoader.loadClass("org.yaml.snakeyaml.Yaml");
Working with Isolated Classes
Reflection with Isolated Classes
// Load class from isolated loader
Class<?> gsonClass = isolatedLoader.loadClass("com.google.gson.Gson");
// Create instance
Object gson = gsonClass.getDeclaredConstructor().newInstance();
// Call methods via reflection
Method toJson = gsonClass.getMethod("toJson", Object.class);
String json = (String) toJson.invoke(gson, myObject);
Method fromJson = gsonClass.getMethod("fromJson", String.class, Class.class);
Object obj = fromJson.invoke(gson, json, MyClass.class);
Interface-based Integration
When possible, use interfaces to work with isolated classes:
// Define interface in main classpath
public interface JsonProcessor {
String toJson(Object obj);
<T> T fromJson(String json, Class<T> type);
}
// Implementation loaded in isolated class loader
public class GsonJsonProcessor implements JsonProcessor {
private final Gson gson = new Gson();
@Override
public String toJson(Object obj) {
return gson.toJson(obj);
}
@Override
public <T> T fromJson(String json, Class<T> type) {
return gson.fromJson(json, type);
}
}
Combining with Relocation
Isolated + Relocated Dependencies
// Create isolated loader
IsolatedClassLoader isolatedLoader = libraryManager.createNamedIsolatedClassLoader("gson-v2");
// Define relocations
List<Relocation> relocations = List.of(
Relocation.of("com.google.gson", "com.myplugin.gson.v2")
);
// Load with both isolation and relocation
libraryManager.loadDependencies(isolatedLoader, dependencies, relocations);
// Use relocated classes in isolated environment
Class<?> gsonClass = isolatedLoader.loadClass("com.myplugin.gson.v2.Gson");
Class Loader Management
Checking Class Loader Status
// Check if class loader is closed
if (!isolatedLoader.isClosed()) {
// Safe to use
Class<?> clazz = isolatedLoader.loadClass("com.example.MyClass");
}
// Get path count (if supported)
int pathCount = isolatedLoader.getPathCount();
if (pathCount > 0) {
System.out.println("Class loader has " + pathCount + " paths");
}
Resource Management
// Use try-with-resources for automatic cleanup
try (IsolatedClassLoader loader = libraryManager.createNamedIsolatedClassLoader("temp")) {
libraryManager.loadDependencies(loader, dependencies);
// Use the loader
Class<?> clazz = loader.loadClass("com.example.MyClass");
// ... work with class
} // Automatically closed here
Managing Multiple Loaders
// Keep track of multiple isolated loaders
Map<String, IsolatedClassLoader> loaders = new HashMap<>();
// Create specialized loaders
loaders.put("json", libraryManager.createNamedIsolatedClassLoader("json-processing"));
loaders.put("xml", libraryManager.createNamedIsolatedClassLoader("xml-processing"));
loaders.put("database", libraryManager.createNamedIsolatedClassLoader("database-drivers"));
// Load different dependencies into each
libraryManager.loadDependencies(loaders.get("json"), List.of(
Dependency.of("com.google.code.gson", "gson", "2.10.1")
));
libraryManager.loadDependencies(loaders.get("xml"), List.of(
Dependency.of("com.fasterxml.jackson.dataformat", "jackson-dataformat-xml", "2.15.2")
));
// Clean up all loaders
loaders.values().forEach(IsolatedClassLoader::close);
Statistics and Monitoring
// Get statistics about isolated class loaders
LibraryManager.LibraryManagerStats stats = libraryManager.getStats();
System.out.println("Active isolated class loaders: " + stats.isolatedClassLoaderCount());
// This count includes both named and global isolated class loaders
Best Practices
When to Use Isolated Class Loaders
- Different library versions: When you need multiple versions of the same library
- Plugin systems: To isolate plugin dependencies from each other
- Optional features: For features that can be enabled/disabled at runtime
- Temporary operations: For one-time operations that need specific dependencies
Edit on GitHub
Last updated on