В недавнем проекте у нас была типичная проблема конфликта библиотек . Один компонент, который мы могли контролировать, хотел иметь конкретную версию библиотеки 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
|
@Override protected 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 classloader DirectoryBasedParentLastURLClassLoader classLoader = new DirectoryBasedParentLastURLClassLoader( ClassLoaderTest.JARS_DIR ); //manually load a specific class Class classManuallyLoaded = 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 — Handmade Classloader Isolation от нашего партнера JCG Паоло Антинори в блоге Someday Never Comes .