Загрузчики классов определяют, когда и каким образом классы могут быть добавлены в работающую систему. Частью их работы является защита важных составляющих системы, например запрет на загрузку поддельного менеджера безопасности. Они выполняют две основные функции – собственно загрузку байт-кода (с локального диска, по сети, из области памяти и т. д.), определение namespaces (пространства имен) для различных классов и способов их взаимодействия (отделяя, к примеру, локальные классы от загруженных по сети).
Есть два вида загрузчиков – primordial (первичный) и Class Loader Object (реализованный в виде объекта). Первичный существует в единственном экземпляре и является составной частью виртуальной машины. Он занимается загрузкой доверенных классов (обычно находящихся на локальном диске). Загрузчик второго типа представляет собой экземпляр обычного Java-класса, унаследованного от абстрактного класса java.lang.ClassLoader. С его помощью можно осуществить загрузку класса по сети либо динамическое конструирование класса приложением. Метод defineClass преобразует массив байтов в экземпляр класса Class, а экземпляры нового класса создаются с помощью метода newInstance класса Class. Если метод созданного класса ссылается на другие классы, виртуальная машина вызывает метод loadClass его загрузчика, передавая ему имя запрашиваемого класса. При создании экземпляра класса или вызове любого из его методов потребуется также загрузка его предка и других используемых им классов, за это отвечает функция resolveClass.
Примерная реализация класса NetworkClassLoader может выглядеть следующим образом:
class NetworkClassLoader
{
String host;
int port;
Hashtable cache = new Hashtable();
NetworkClassLoader(String aHost, int aPort)
{
host = aHost;
port = aPort;
}
private byte loadClassData(String name)[]
{
// собственно загрузка класса
. . .
}
public synchronized Class loadClass(String name, boolean resolve)
{
Class c = cache.get(name);
// Хэш-таблица используется для исключения
if (c == null)
// повторной загрузки класса
{
// и формирования пространства имен
byte data[] = loadClassData(name);
c = defineClass(data, 0, data.length);
cache.put(name, c);
}
if (resolve)
resolveClass(c);
return c;
}
}
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
На самом деле загрузчик сначала должен обратиться к пространству имен первичного загрузчика и затем, лишь не обнаружив там запрашиваемого класса, продолжить поиск по пространству имен ссылающегося класса, чтобы предотвратить подделку встроенных классов. Причем загрузчик работает совместно с менеджером безопасности, определяющим, можно ли загружать данный класс. Весь алгоритм действий загрузчика обычно выглядит следующим образом:
1. Определить, не был ли загружен этот класс раньше, и, если да, вернуть его.
2. Проконсультироваться с первичным загрузчиком на предмет существования внутреннего класса с этим именем.
3. Запросить у менеджера безопасности разрешение на загрузку данного класса.
4. Считать файл класса в виде массива байтов – по сети, с диска и т. п.
5. Создать экземпляр класса Class.
6. Загрузить используемые классы.
7. Передать класс верификатору на проверку.
Загрузчик является существенным элементом модели безопасности, поэтому писать загрузчики следует особо осторожно – малейшая ошибка может привести к полному краху всей системы безопасности. В нормальной ситуации апплеты не имеют возможности устанавливать свои загрузчики.
Класс SecurityManager отвечает за политику безопасности приложения. Он позволяет приложению перед выполнением потенциально опасной операции выяснить, выполняется ли она классом, загруженным первичным загрузчиком, либо с помощью некоторого ClassLoader (к последнему, особенно при загрузке из сети, доверия должно быть гораздо меньше). Далее менеджер безопасности может определить, разрешить ли эту операцию или наложить на нее вето. Класс SecurityManager выявляет ряд методов, начинающихся со слова «check» (checkDelete, checkExec, checkConnect и т. п.), которые вызываются всеми методами стандартной библиотеки, выполняющими потенциально опасные действия (работа с файлами, сетевыми соединениями и т. п.). Выглядит это обычно следующим образом:
SecurityManager security = System.getSecurityManager();
if (security != null)
{
security.checkXXX(argument, . . . );
}
При разрешении операции функция check просто возвращает управление, при запрещении – возбуждает исключение SecurityException. Реализация по умолчанию для любого метода check* предполагает, что вызываемый метод не имеет права на выполнение данной операции. В обязанности менеджера безопасности, работающего с апплетами, входит защита от загрузки новых загрузчиков классов, защита потоков и групп потоков друг от друга, контроль за обращением к системным ресурсам, к ресурсам виртуальной машины, к сетевым соединениям и т. п.
Читать дальше
Конец ознакомительного отрывка
Купить книгу