在最近的项目中,我们遇到了一个典型的库冲突问题 。 我们可以控制的一个组件需要特定版本的Apache Commons库,而另一个组件则需要一个不同的版本。 由于外部限制, 我们无法在Container级别指定任何类加载隔离 。 这不是我们的选择。 相反,我们决定要做的是同时使用两个不同的类定义。 为此,我们必须让当前线程类加载器加载一个类,并手动加载第二个 ; 这样,这两个类仍具有相同的完全限定名称。
这种方法的唯一限制是,我们只能通过反射与手动加载的类进行交互,因为当前上下文使用的是不同的类加载器,
具有不同的类定义,我们可以将加载有类加载器的类的实例强制转换或分配给在另一个上下文中定义的变量。 实际上,我们的实现是Classloader本身:
DirectoryBasedParentLastURLClassLoader extends ClassLoader
这个Classloader的特点是我们给它传递了一个文件系统文件夹路径 :
public DirectoryBasedParentLastURLClassLoader(String jarDir)
我们的实现扫描文件系统路径以生成URL,并使用此信息将它们传递给我们用CustomClassloader封装的URLClassLoader的包装实例 :
public DirectoryBasedParentLastURLClassLoader(String jarDir) {super(Thread.currentThread().getContextClassLoader());// search for JAR files in the given directoryFileFilter jarFilter = new FileFilter() {public boolean accept(File pathname) {return pathname.getName().endsWith('.jar');}};// create URL for each JAR file foundFile[] jarFiles = new File(jarDir).listFiles(jarFilter);URL[] urls;if (null != jarFiles) {urls = new URL[jarFiles.length];for (int i = 0; i < jarFiles.length; i++) {try {urls[i] = jarFiles[i].toURI().toURL();} catch (MalformedURLException e) {throw new RuntimeException('Could not get URL for JAR file: ' + jarFiles[i], e);}}} else {// no JAR files foundurls = new URL[0];}childClassLoader = new ChildURLClassLoader(urls, this.getParent());
}
通过此设置,我们可以覆盖主要类加载功能的行为,仅当能够找到请求的类时,才优先考虑从文件夹中进行加载并回落到父类加载器 :
@Override
protected synchronized ClassloadClass(String name, boolean resolve)throws ClassNotFoundException {try {// first try to find a class inside the child classloaderreturn childClassLoader.findClass(name);} catch (ClassNotFoundException e) {// didn't find it, try the parentreturn super.loadClass(name, resolve);}
}
有了我们的CustomClassloader之后,我们可以通过以下方式使用它:
//instantiate our custom classloader
DirectoryBasedParentLastURLClassLoader classLoader = new DirectoryBasedParentLastURLClassLoader(ClassLoaderTest.JARS_DIR );
//manually load a specific class
ClassclassManuallyLoaded = classLoader.loadClass('paolo.test.custom_classloader.support.MyBean');
//request a class via reflection
Object myBeanInstanceFromReflection = classManuallyLoaded.newInstance();
//keep using the class via reflection
Method methodToString = classManuallyLoaded.getMethod('toString');
assertEquals('v1', methodToString.invoke(myBeanInstanceFromReflection));
这篇文章的想法及其部分代码来自有关Stackoverflow的有趣讨论。 GitHub上提供了一个可以正常运行的Maven项目,并进行了大量的单元测试以验证正确的行为。
参考: Java –我们的JCG合作伙伴 Paolo Antinori的Handmade Classloader Isolation在Someday Never Comes博客上发表。
翻译自: https://www.javacodegeeks.com/2013/03/java-handmade-classloader-isolation.html