Статьи

Java — Изоляция загрузчика классов ручной работы

В недавнем проекте у нас была типичная проблема конфликта библиотек . Один компонент, который мы могли контролировать, хотел иметь конкретную версию библиотеки 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 .