Родитель-дочерний класс Classloader Разрешение
Задачи и первоначальное расследование
Я пытаюсь настроить два Oracle Coherence рядом с экземплярами кэша в одном приложении Java Swing. Идея решения может быть найдена здесь. Мой случай немного сложнее, и именно здесь начинается игра.
Краткое описание
В моем случае есть сервис аккаунта. Может иметь две конечные точки: SIT и UAT. Чтобы создать два таких сервиса, мне нужно загрузить два "экземпляра" Coherence, чтобы переопределить конечные точки системными переменными (tangosol.coherence.cacheconfig).
Я имею:
- основной код приложения находится в mainapp.jar;
- интерфейс AccountService, который находится в account-interfaces.jar;
- класс AccountServiceImpl, который находится в account-impl.jar и реализует интерфейс AccountService;
- мое основное приложение имеет следующую структуру
bin: startup.bat, startup.sh conf: app.properties lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
Подход пробовал
Я создал отдельный дочерний класс classLoader - InverseClassLoader и сделал AppLaunchClassLoader (по умолчанию Thread.currentThread().GetContextClassLoader() classLoader) его родителем. С InverseClassLoader я загружаю класс AccountServiceImpl:
Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class);
AccountService acService = acConstructor .newInstance(serviceURL);
Проблемы и вопросы
- Я получаю исключения "AccountServiceImpl не может быть приведен к AccountService", что означает, что эти два класса загружены разными загрузчиками классов. Но эти загрузчики классов находятся в отношениях родитель-ребенок. Итак, я прав, что даже если класс загружается родительским (интерфейс - "абстрактный" тип), он не может использоваться с классом (конкретный импл), загруженным дочерним загрузчиком классов? Зачем тогда нам нужно это родительско-дочернее отношение?
- Я указал интерфейс AccountService в коде, и он был загружен загрузчиком классов по умолчанию. Я попытался обернуть код выше потоком и установить InverseClassLoader это контекстный загрузчик классов. Ничего не изменилось. Итак, я прав, что я не могу использовать такое кодирование реализации интерфейса (как обычное кодирование) и должен постоянно использовать отражение, чтобы постоянно вызывать конкретные методы? (Надеюсь, что есть решение);
- Скажем, я перечислил оба класса AccountService и AccountServiceImpl для загрузки InverseClassLoader. Что если мне понадобятся другие классы, доступные этим двум, чтобы они также загружались InverseClassLoader? Есть ли способ сказать, что все "связанные" классы должны быть загружены одним и тем же загрузчиком классов?
Обновить
Вот InverseClassLoader:
public class InvertedClassLoader extends URLClassLoader {
private final Set<String> classesToNotDelegate = new HashSet<>();
public InvertedClassLoader(URL... urls) {
super(urls, Thread.currentThread().getContextClassLoader());
}
public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
classesToNotDelegate.add(classToNotDelegate.getName());
return this;
}
@Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (shouldNotDelegate(className)) {
System.out.println("CHILD LOADER: " + className);
Class<?> clazz = findClass(className);
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
else {
System.out.println("PARENT LOADER: " + className);
return super.loadClass(className, resolve);
}
}
public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
final Class<?> clazz = loadClass(classToLoad.getName());
@SuppressWarnings("unchecked")
final Class<T> castedClass = (Class<T>) clazz;
return castedClass;
}
private boolean shouldNotDelegate(String className) {
if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
return true;
}
return false;
}
1 ответ
Выпуск 1, часть первая, я не могу воспроизвести (см. Ниже). Что касается части 2: иерархия загрузчиков классов предназначена для предотвращения исключений "X не может быть приведен к X". Но если вы нарушите правило "родитель-первый", у вас могут возникнуть проблемы.
О проблеме 2: установка загрузчика классов контекста потока сама по себе ничего не делает, см. Также эту статью (javaworld.com) для получения дополнительной информации. Также, в связи с проблемой 1, часть 2, цитата из статьи, которая описывает, что может произойти, если между текущим загрузчиком классов и загрузчиком классов контекста потока нет отношения родитель-потомок:
Помните, что загрузчик классов, который загружает и определяет класс, является частью внутреннего идентификатора JVM для этого класса. Если текущий загрузчик классов загружает класс X, который впоследствии выполняет, скажем, поиск JNDI для некоторых данных типа Y, загрузчик контекста может загрузить и определить Y. Это определение Y будет отличаться от определения с тем же именем, но видимым текущим погрузчик. Введите скрытые исключения приведения классов и ограничения загрузчика.
Ниже приведена простая демонстрационная программа, показывающая, что приведение к интерфейсу из другого загрузчика классов может работать (обратите внимание, что я использую простой проект Java с классами в папке bin и InvertedClassLoader
из вашего вопроса в том же (тестовом) пакете):
import java.io.File;
public class ChildFirstClassLoading {
public static void main(String[] args) {
InvertedClassLoader cl = null;
try {
File classesDir = new File(new File("./bin").getCanonicalPath());
System.out.println("Classes dir: " + classesDir);
cl = new InvertedClassLoader(classesDir.toURI().toURL());
cl.selfLoad(CTest.class);
System.out.println("InvertedClassLoader configured.");
new CTest("Test 1").test();
ITest t2 = cl.loadClass(CTest.class)
.getConstructor(String.class)
.newInstance("Test 2");
t2.test();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cl != null) {
try { cl.close(); } catch (Exception ignored) {}
}
}
}
public interface ITest {
void test();
}
public static class CTest implements ITest {
static {
System.out.println("CTest initialized.");
}
private String s;
public CTest(String s) {
this.s = s;
}
public void test() {
System.out.println(s);
}
}
}
Если вы измените ITest t2 =
в CTest t2 =
вы получите исключение "CTest не может быть приведен к CTest", но использование интерфейса предотвращает это исключение. Поскольку эта небольшая демонстрация работает нормально, я предполагаю, что в вашем приложении происходит больше, что каким-то образом нарушает загрузку классов. Я предлагаю вам работать в ситуации, когда загрузка классов работает, и продолжайте добавлять код, пока он не прервет загрузку классов.
InvertedClassLoader
выглядит очень похоже на "child first classloader", посмотрите этот вопрос, чтобы найти хорошие ответы на этот вопрос о загрузке классов. Дочерний загрузчик первого класса может использоваться для загрузки "связанных классов" (из вашего третьего выпуска) отдельно. Вы также можете обновить InvertedClassLoader
всегда "самостоятельно загружать" классы в определенных пакетах. И помните, что "как только класс загружается загрузчиком классов, он использует этот загрузчик классов для загрузки всех других классов, которые ему нужны" (цитата из этой статьи блога).