У меня есть веб-служба, которую мы назовем service.war. Он реализует интерфейс, который мы назовем ServicePluginInterface. Во время запуска service.war он считывает переменные среды и использует их для поиска jar-файла (MyPlugin.jar). Когда он находит эту банку, он затем использует вторую переменную среды для загрузки плагина в банку. Класс, который он загружает, выглядит так:

public class MyPlugin implements ServicePluginInterface {...}

Сервлет пытается загрузить плагин, используя такой код:

try {
        if (pluginClass == null) {
            plugin = null;
        }
        else {
            ZipClassLoader zipLoader = new ZipClassLoader(Main.class.getClassLoader(), pluginJar);
            plugin = (ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
            plugin.getAccount(null,null);
        }
    } catch (Exception e) {
        ...
    }

Хитрость в том, что у меня нет исходного кода или jar-файла для ServicePluginInterface. Не желая так легко сдаваться, я вытащил файлы классов из файлов service.war. Используя эти файлы классов в качестве зависимостей, я смог без предупреждений компилятора создать MyPlugin. Однако, когда на самом деле выполняется Tomcat, приведенный выше раздел кода генерирует исключение времени выполнения:

java.lang.ClassCastException: com.whatever.MyPlugin cannot be cast to com.whomever.ServicePluginInterface

В качестве второй точки отсчета я также могу создать синтетический загрузчик классов (отдельный исполняемый файл Java, который использует тот же механизм загрузки классов. Опять же, поскольку у меня нет исходного источника для ServicePluginInterface, я использовал файлы классов из WAR Этот второй, синтетический загрузчик, или, если хотите, фальшивый сервлет, МОЖЕТ загрузить MyPlugin очень хорошо.Поэтому я бы предположил, что Tomcat JVM, похоже, обнаруживает некоторую разницу между классами, найденными внутри WAR, и извлеченными файлами классов. Однако, поскольку все, что я сделал для извлечения файлов классов, - это открыть WAR в виде zip-архива и скопировать их, трудно представить, что это может быть.


Хавьер сделал полезное предложение об удалении определения ServicePluginInterface, проблема с этим решением заключалась в том, что ZipClassLoader, который сервлет использует для загрузки подключаемого модуля из jar-файла, переопределяет функцию ClassLoader findClass для извлечения класса из JAR следующим образом:

protected Class<?> findClass(String name) throws ClassNotFoundException
{
ZipEntry entry = this.myFile.getEntry(name.replace('.', '/') + ".class");

if (entry == null) {
  throw new ClassNotFoundException(name);
}
...
}

Затем класс ZipClassLoader рекурсивно загружает все родительские объекты и интерфейсы из jar . Это означает, что если jar-файл плагина не содержит определения для ServicePluginInterface, он завершится ошибкой.

1
OverclockedTim 18 Дек 2013 в 06:41
Почему ZipClassLoader не делегируется своему родительскому элементу? Какая это реализация?
 – 
Javier
18 Дек 2013 в 12:17
К сожалению, я думаю, что авторские права на ZipClassLoader принадлежат владельцу сервлета, поэтому я не могу поделиться им полностью, но теперь мне интересно, можно ли написать лучший ZipClassLoader и заменить файл класса в WAR .. .
 – 
OverclockedTim
18 Дек 2013 в 19:49

1 ответ

Лучший ответ

Классы, определенные разными загрузчиками классов, разные :

Во время выполнения может быть несколько ссылочных типов с одним и тем же двоичным именем. загружаются одновременно разными загрузчиками классов. Эти типы могут или может не представлять одно и то же объявление типа. Даже если два таких типа делают представляют одно и то же объявление типа, они считаются разными. JLS

В этом случае zipLoader возвращает экземпляр MyPlugin, который реализует другое ServicePluginInterface (загружается ли он тоже из zip-архива?):

(ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();

Кажется, что сервер приложений уже имеет определение ServicePluginInterface, поэтому вам не нужно его повторно развертывать. Этого должно быть достаточно, чтобы добавить необходимые файлы (ServicePluginInterface и т. Д.) Как неразвернутые зависимости вашего проекта.

Другой подход заключается в том, чтобы жить с фактом и обращаться к методам в ServicePluginInterface через отражение (используйте объект Class, возвращаемый zipLoader, вместо ServicePluginInterface.class).

1
Javier 18 Дек 2013 в 08:37
Очень полезный комментарий о загрузчиках классов - я подозреваю, что настоящая уловка кроется где-то в этой области. К сожалению, и по причинам, ранее неизвестным вам, я считаю, что это не решит мою проблему из-за характера ZipClassLoader. Я добавил детали в конец вопроса, где они могут быть красиво отформатированы, но суть в том, что ZipClassLoader не работает, если интерфейс также не найден в JAR. Что касается второго предложения, к сожалению, невозможно изменить код загрузчика сервлета, поскольку он был предоставлен третьей стороной, и у меня нет исходного кода.
 – 
OverclockedTim
18 Дек 2013 в 12:04
Поэтому я отмечу ваш ответ как принятый, так как это, вероятно, более универсальное и полезное решение. В конечном итоге я полностью перезаписал оба файла класса ZipClassLoader одним из моих собственных, который напрямую загружает (без открытия JAR) класс плагина - который, конечно, потом мне также пришлось разместить внутри WAR. Уродливый хак, но сработало ...
 – 
OverclockedTim
20 Дек 2013 в 00:40