В недавнем проекте у нас была типичная проблема конфликта библиотек . Один компонент, который мы могли контролировать, хотел иметь конкретную версию библиотеки Apache Commons, в то время как другой компонент ожидал другую. Из-за внешних ограничений мы не смогли указать какую-либо изоляцию загрузки класса на уровне контейнера . Это был не вариант для нас. Вместо этого мы решили использовать два разных определения классов одновременно. Чтобы получить это, мы должны были разрешить загрузку одного класса текущим загрузчиком класса потока и вручную загрузить второй ; таким образом, два класса по-прежнему имеют одно и то же полное имя.
Единственным ограничением этого подхода является то, что нам приходилось взаимодействовать с загруженным вручную классом только с помощью отражения, поскольку текущий контекст, который использует другой загрузчик классов,
имеет другое определение класса, и мы могли бы привести или присвоить экземпляр класса, загруженного загрузчиком классов, переменной, определенной в контексте другого. Наша реализация в действительности сама Classloader:
|
1
|
DirectoryBasedParentLastURLClassLoader extends ClassLoader |
Характеристика этого Classloader заключается в том, что мы передаем ему путь к папке файловой системы :
|
1
|
public DirectoryBasedParentLastURLClassLoader(String jarDir) |
Наша реализация сканирует путь к файловой системе для создания URL-адресов и использует эту информацию для передачи их в упакованный экземпляр URLClassLoader, который мы инкапсулируем с помощью нашего CustomClassloader:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public DirectoryBasedParentLastURLClassLoader(String jarDir) { super(Thread.currentThread().getContextClassLoader()); // search for JAR files in the given directory FileFilter jarFilter = new FileFilter() { public boolean accept(File pathname) { return pathname.getName().endsWith('.jar'); } }; // create URL for each JAR file found File[] 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 found urls = new URL[0]; } childClassLoader = new ChildURLClassLoader(urls, this.getParent());} |
С помощью этой настройки мы можем переопределить поведение основной функции загрузки классов, отдавая приоритет загрузке из нашей папки и возвращаясь к родительскому загрузчику классов, только если мы сможем найти запрошенный класс:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
@Overrideprotected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { try { // first try to find a class inside the child classloader return childClassLoader.findClass(name); } catch (ClassNotFoundException e) { // didn't find it, try the parent return super.loadClass(name, resolve); }} |
С нашим CustomClassloader мы можем использовать его следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
//instantiate our custom classloaderDirectoryBasedParentLastURLClassLoader classLoader = new DirectoryBasedParentLastURLClassLoader( ClassLoaderTest.JARS_DIR );//manually load a specific classClass classManuallyLoaded = classLoader .loadClass('paolo.test.custom_classloader.support.MyBean');//request a class via reflectionObject myBeanInstanceFromReflection = classManuallyLoaded.newInstance();//keep using the class via reflectionMethod methodToString = classManuallyLoaded.getMethod('toString');assertEquals('v1', methodToString.invoke(myBeanInstanceFromReflection)); |
Эта идея для этого поста и части его кода взята из этого интересного обсуждения Stackoverflow. На GitHub доступен полностью работающий проект Maven с кучей модульных тестов для проверки правильности поведения.
Ссылка: Java — Handmade Classloader Isolation от нашего партнера JCG Паоло Антинори в блоге Someday Never Comes .