Приветствую!
Сегодня нас ждет немного мистики :)
Недавно так сложилось, что в одном проекте понадобилось одну библиотеку сделать подгружаемой динамически. И это не было бы проблемой (особенно если вы читали статью про загрузку JAR из другого JAR), но вот беда - надо было сделать так, чтобы ее не "видело" наше приложение. А знал бы про нее только класс, из которого мы загрузили библиотеку ну и те классы, которые подгружались вместе с библиотекой.
И еще одной проблемой было то, что в библиотеке дублировались некоторые классы из других библиотек. И вот эти дублированные классы должны быть доступны библиотеке (причем именно они, а не классы из отдельных библиотек). А приложению доступны были только классы из отдельных библиотек, а не классы динамической библиотеки.
Запутал? :)
Объясню на примере:
Пусть наша динамическая библиотека называется jar_1.jar. Она содержит классы Class_1, Class_2, Class_3, Class_4.
Кроме того если статические библиотеки jar_2.jar (с классами Class_1, Class_11, Class_12) и jar_3.jar (с классами Class_2, Class_20, Class_21, Class_22).
Так вот, jar_1 должна видеть jar_1!/Class_1, а не jar_2!/Class_1; и jar_1!/Class_2, а не jar_3!/Class_2.
И при этом, jar_1 должна видеть jar_2!/Class_11, jar_2!/Class_12, jar_3!/Class_20, jar_3!/Class_21, jar_3!/Class_22.
А приложение должно видеть jar_2!/Class_1 и jar_3!/Class_2. И не видеть все классы из jar_1.
Единственный выход - собственный ClassLoader с измененной логикой загрузки классов.
За основу мы взяли URLClassLoader и через него мы и подгружаем нашу библиотеку, которая, чтобы быть невидимой для всех остальных ClassLoader-ов находилась в приложении в виде ресурса.
Теперь оставалось только сделать верную логику загрузки классов, чтобы классы из обычных библиотек были прекрасно видны нашей библиотекой. А классы нашей библиотеки не становились бы доступны остальному приложению.
Результат:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = null;
try {
c = findLoadedClass(name);
} catch (Exception e) {
// swallow
}
if (c == null) {
try {
c = findClass(name);
} catch (Exception e) {
// swallow
}
}
if ((c == null) && (getParent() != null)) {
try {
c = getParent().loadClass(name);
} catch (Exception e) {
// swallow
}
}
if (c == null) {
c = findSystemClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
При этом надо не забыть передать в конструктор текущий ClassLoader как родителя:
new MagicURLClassLoader(new URL[] {
getInsideJarURL(getClass().getClassLoader(), "magic.jar")}, getClass().getClassLoader());