пятница, 27 июля 2012 г.

Общение с потусторонним миром или динамически-подгружаемая библиотека, невидимая для остального приложения

Приветствую!

Сегодня нас ждет немного мистики :)

Недавно так сложилось, что в одном проекте понадобилось одну библиотеку сделать подгружаемой динамически. И это не было бы проблемой (особенно если вы читали статью про загрузку 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());

Комментариев нет:

Отправить комментарий