Полезное для программистов:

Фриланс
Новости
Статьи
   
Рубрики:


Разработка собственного загрузчика классов

Поиск:
Механизм динамической загрузки классов – одна из основных причин по которой платформа Java приобрела значительную популярность. Загрузчики классов (classloaders) как раз и обеспечивают возможность расширения. Стандартный загрузчик java-машины используется для загрузки классов из каталогов или zip-архивов (правда, обычно имеющих расширение *.jar), определенных в системном свойстве java.class.path, содержимое которого определяется с помощью системной переменной CLASSPATH или установки ключа -cp при запуске java-машины. 

Задача создания собственного загрузчика классов возникает довольно часто. Стоит отметить, что собственные загрузчики классов используют все серверы приложений м web-контейнеры, что и понятно – приложения, разворачиваемые на сервере приложений, должны загружаться динамически, в противном случае перечисление в переменной CLASSPATH всех библиотек, используемых приложениями, становится задачей нетривиальной. Скажу больше, серверы приложений, как правило, используют не один загрузчик классов, а целую их иерархию. Если проанализировать какие загрузчики используют компоненты EJB и сервлеты, то, скорее всего, окажется, что они совершенно разные. Собственный загрузчик классов использует Jakarta Ant и множество других приложений, библиотек и серверов.

Ну хорошо, скажете вы, а мне-то это зачем? Необходимость в собственном загрузчике классов возникает достаточно часто в следующих случаях:
  • нет возможности или нежелательно перечислять все используемые библиотеки при старте программы в CLASSPATH;
  • возможностей стандартного загрузчика недостаточно для загрузки нужных классов.
У меня необходимость в своем загрузчике возникла при написании системы плагинов для приложения. Естественно, удобнее просто положить все, что относится к плагину, в отдельный каталог, чем прописывать используемые плагинами библиотеки каждый раз когда их набор меняется. 

Что делать, если библиотеки доступны только удаленно, например, находятся на веб-сервере? Написать собственный загрузчик? На самом деле, в этом нет необходимости. Класс java.net.URLClassLoader, подклассом которого является стандартный загрузчик JVM, позволяет загружать классы не только из ресурсов, находящихся в файловой системе, но и из ресурсов, находящихся в сети, задавая URL. Однако, встречаются такие ситуации, когда и его возможностей недостаточно.

При загрузке классов платформа Java использует механизм делегирования. Основная идея заключается в том, что каждый загрузчик классов имеет «родительский» загрузчик. В процессе загрузки классов загрузчик в первую очередь делегирует задачу поиска родительскому объекту-загрузчику прежде чем пытаться самому найти класс. При разработке загрузчика класса необходимо соблюдать этот принцип.

Итак, поставим себе следующую задачу. Необходимо реализовать загрузчик, который может расширяться динамически, то есть в процессе работы приложения добавлять или удалять элементы CLASSPATH.

Любой используемый в JVM загрузчик должен расширять java.lang.ClassLoader и переопределить в нем методы findClass() и findResource() (второй только в том случае если есть необходимость загружать не только классы, но и ресурсы). 
Код

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

/**
 * Класс реализует <code>ClassLoader</code>, позволяющий динамически расширять
 * маршрут поиска классов и ресурсов 
 */
public class ExtendableClassLoader extends ClassLoader {

    private Hashtable cache;

    private ArrayList paths;

    private ClassLoader currentLoader = null;

    public ExtendableClassLoader() {
        cache = new Hashtable();
        paths = new ArrayList();
    }
...
}

Здесь атрибут cache будет использоваться для хранения уже найденных классов (не искать же их в самом деле при каждом обращении), paths – список путей поиска классов, включающий в себя каталоги и библиотеки, currentLoader будет хранить ссылку на дефолтный загрузчик классов.
Код

    /**
     * @see java.lang.ClassLoader#findClass(java.lang.String)
     */
    protected synchronized Class findClass(String className) throws ClassNotFoundException {
        Class result;
        byte classData[];
        
        // Проверяем кэш классов. Если класса в кэше нет, тогда применяются другие средства
        result = (Class) cache.get(className);
        if (result != null) {
            return result;
        }

        // Проверяется нет ли такого класса в стандартном пути 
        try {
            
            // если загрузчик классов текущего потока уже является расширенным,
            // используем сохраненное значение загрузчика классов
            if (Thread.currentThread().getContextClassLoader() instanceof ExtendableClassLoader) {
                if (this.currentLoader == null) throw new ClassNotFoundException();
                else result = this.currentLoader.loadClass(className);
            } else
                result = Thread.currentThread().getContextClassLoader().loadClass(className);
            return result;
        } catch (ClassNotFoundException e) {
        } catch (Exception e) {
        }
        
        // Попытка загрузить класс из добавленного пути
        classData = getClassFromAddedClassPaths(className);
        if (classData == null) {
            throw new ClassNotFoundException();
        }

        // определение класса
        result = super.defineClass(className, classData, 0, classData.length);
        if (result == null) throw new ClassFormatError();

        resolveClass(result);

        // Полученный класс добавляется в кэш классов
        cache.put(className, result);

        return result;
    }

Прежде чем искать класс в путях, зарегистрированных внутри загрузчика, пытаемся найти класс сначала в кэше классов, затем в стандартном загрузчике и только если класс найти не удается загрузчик сам пытается найти класс в заданных путях. Поиск класса выполняется в методе getClassFromAddedClassPaths(). Результатом работы этого метода должен быть массив байтов, представляющий нужный класс. Из массива байтов создается объект типа java.lang.Class, для чего можно использовать метод defineClass(), который реализован в  java.lang.ClassLoader.
Код

    /**
     * Метод ищет класс в созданном CLASSPATH и возвращает массив байтов
     *
     * @param className Имя класса, который нужно загрузить
     * @return Массив, содержащий байт-код класса
     */
    private byte[] getClassFromAddedClassPaths(String className) {
        try {
            String fsep = System.getProperty("file.separator");
            for (String path : paths) {
                File pathFile = new File(path);
                if (pathFile.isDirectory()) {
                    File f = new File(path + fsep
                            + classNameToFileName(className));
                    if (f.exists()) {
                        FileInputStream fis = new FileInputStream(f);
                        return createByteArray(fis);
                    }
                } else {
                    JarFile jarFile = new JarFile(pathFile);
                    ZipEntry entry = jarFile
                            .getEntry(classNameToZipEntryName(className));
                    if (entry == null) {
                        continue;
                    }
                    InputStream stream = jarFile.getInputStream(entry);
                    return createByteArray(stream);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Создает массив байтов из входного потока
     * @param in Входной поток
     * @return Массив байтов
     * @throws IOException
     */
    private byte[] createByteArray(InputStream in) throws IOException {
        final int bufferSize = 2048;
        byte result[] = new byte[bufferSize];
        
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int len = 0;
        while ((len = in.read(result)) != -1) out.write(result, 0, len);
        return out.toByteArray();
    }

    /**
     * Преобразует имя пакета в путь к каталогу
     *
     * @param className преобразуемое имя класса
     * @return полученное имя файла
     */
    private String classNameToFileName(String className) {
        return className.replace(
            '.',
            System.getProperty("file.separator").charAt(0))
            + ".class";
    }

    private String classNameToZipEntryName(String className) {
        return className.replace('.', '/')
        + ".class";
    }

Теперь переопределим метод findResource(), предназначенный для поиска ресурсов. В отличие от findClass() он возвращает объекта класса java.net.URL.
Код

    protected URL findResource(String name) {
        URL res = ClassLoader.getSystemResource(name);
        if (res != null)
            return res;
        try {
            String fsep = System.getProperty("file.separator");
            for (String path : paths) {
                File pathFile = new File(path);
                if (pathFile.isDirectory()) {
                    File f = new File(path + fsep + name);
                    if (f.exists())
                        return f.toURL();
                } else {
                    JarFile jarFile = new JarFile(pathFile);
                    ZipEntry entry = jarFile.getEntry(name);
                    if (entry == null) continue;
                    String url = createJarResourceURL(pathFile, name, "/");
                    return new URL(url);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    private String createJarResourceURL(File jarFile, String resourceName, String fsep) throws MalformedURLException {
        String url = "jar:" + jarFile.toURL() + "!" + fsep + resourceName;
        return url; 
    }

Ну и, наконец, реализуем набор методов, которые позволяли бы изменять список путей, в которых хранятся классы. 
Код

    /**
     * Добавляет строку в CLASSPATH
     *
     * @param path Добавляемая строка
     */
    public void addClassPath(String path) {
        paths.add(path);
    }

    /**
     * Удаляет строку из CLASSPATH
     *
     * @param path Удаляемая строка
     */
    public void removeClassPath(String path) {
        int index = paths.indexOf(path);
        paths.remove(index);
    }

Часто возникает необходимость сделать свой загрузчик классов дефолтным, поэтому неплохо было предусмотреть метод, позволяющий это сделать.
Код

    /**
     * Устанавливает новый загрузчик классов для текущего потока, используя 
     * данный экземпляр класса расширенного загрузчика. Перед установкой
     * выполняется проверка не был ли уже установлен такой загрузчик
     */    
    public void setCurrentThreadClassLoader() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader instanceof ExtendableClassLoader) return;
        else {
            currentLoader = loader;
            Thread.currentThread().setContextClassLoader(this);
        }
    }

В атрибуте currentLoader сохраняем ссылку на текущий дефолтный загрузчик поскольку классы нужно будет искать не только в зарегистрированных нами ресурсах, но и в стандартном CLASSPATH.
Теперь проверим как работает вся конструкция.
Код

public class ClassLoaderTest {

    public static void main(String[] args) throws Exception {
        new ClassLoaderTest().go();
    }
    
    private void go() throws Exception {
        ExtendableClassLoader ecl = new ExtendableClassLoader();
        ecl.addClassPath("lib/commons-lang-2.0.jar");
        Class c = ecl.loadClass("org.apache.commons.lang.math.RandomUtils");
    }

}

Если путь к библиотеке указан правильно, все должно выполниться безо всяких сообщений. В противном случае программа выдаст исключение java.lang.ClassNotFoundException.  
Автор: tux






Просмотров: 4861

 

 

Новые статьи:


Популярные:
  1. Как сделать цикличным проигрывание MIDI-файла?
  2. Создание AVI файла из рисунков
  3. Как устройство "отключить в данной конфигурации"?
  4. Kто в данный момент присоединен через Сеть?
  5. Как узнать количество доступной памяти?
  6. Как реализовать в RichEdit разноцветный текст?
  7. Как скрыть свое приложение от ProcessViewer
  8. Как программно нажать/скрыть/показ кнопку "Start"?
  9. Модуль работы с ресурсами в PE файлах
10. Функции вызова диалоговых окон выбора
11. Проверка граматики средствами Word'а из Delphi.
12. Модуль для упрощенного вызова сообщений
13. Функции для записи и чтение своих данных в, ЕХЕ- файле
14. Рекурсивный просмотр директорий
15. Network Traffic Monitor
16. Разные модули
17. Универсальная функция для обращения к любым экспортируем функциям DLL
18. Библиотека от VladS
19. Протектор для UPX'а
20. Еще об ICQ, сообщения по контакт листу?
21. Использование открытых интерфейсов
22. Теория и практика использования RTTI
23. Работа с TApplication
24. Примеры использования Drag and Drop для различных визуальных компонентов
25. Что такое порт? Правила для работы с портами
26. Симфония на клавиатуре
27. Загрузка DLL
28. Исправление автоинкремента
29. Взаимодействие с чужими окнами
30. Проверить дубляжи в столбце


 

 

 
 
На главную