Главная » Java » Загрузка классов

0

Runtime-система  Java обращается к классам, когда в этом возникает необходимость. Подробности загрузки классов могут отличаться для различных реализаций Java, однако в большинстве случаев используется механизм “пути класса” для поиска компилированного байт-кода класса, используемого в программе, но не загружавшегося ранее. Во многих случаях этот стандартный механизм работает отлично, однако немалая часть достоинств Java обусловлена возможностью реализовать загрузку классов с учетом специфики приложения. Чтобы написать программу, в которой механизм загрузки классов отличается от стандартного, необходимо создать объект ClassLoader, который получает байт-коды классов и загружает их во время выполнения программы.

Например, вы разрабатываете игру, в которой играющие могут создавать собственные классы, использующие выбранную ими стратегию. Для этого вы создаете абстрактный класс Player, расширяемый игроками для реализации своих идей. Когда игроки будут готовы испытать свою стратегию, они пересылают скомпилированный  байт-код класса в вашу систему. Байт-код необходимо загрузить в игру, применить и вернуть игроку его результат (score). Схема выглядит следующим образом:

На сервере игровая программа загружает каждый ожидающий своей очереди класс Player, создает объект нового типа и дает ему возможность противопоставить  свою стратегию игровому алгоритму. Когда выясняется результат, он сообщается игроку—разработчику стратегии.

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

Наибольший интерес представляет процесс загрузки игровой программой скомпилированных  классов. Здесь всем заправляет загрузчик классов. Чтобы создать загрузчик классов, следует расширить абстрактный класс Class Loader и реализовать в подклассе метод loadClass:

protected abstract Class loadClass(String  name, boolean resolve) throws

ClassNotFoundException

Загружает класс с заданным именем name. Если значение resolve равно true, то метод должен вызвать resolveClass, чтобы обеспечить загрузку всех классов, используемых внутри заданного.

В нашем примере мы создадим класс PlayerLoader, предназначенный  для чтения байт- кода классов со стратегией игры и подготовки их к работе. Основной цикл будет выглядеть следующим образом:

public class Game {

static public void main(String[] args) { String name;    // имя класса

while ((name = getNextPlayer()) != null) {

try {

PlayerLoader loader = new PlayerLoader(); Class

classOf = loader.loadClass(name, true);

Player

player = (Player)classOf.newInstance(); Game game = new Game();

player.play(game);

game.reportScore(name);

} catch (Exception e) {

reportException(name, e);

}

}

}

}

Для каждой новой игры нужен свой объект-загрузчик  PlayerLoader; следовательно, новый класс Player не будет смешиваться с классами, загруженными ранее. Новый PlayerLoader загружает класс и возвращает представляющий  его объект Class, который используется для создания нового объекта класса Player. Затем мы создаем новую игру game и играем

в нее. После ее завершения возвращается результат.

Класс PlayerLoader расширяет класс ClassLoader и задает собственную реализацию метода

loadClass:

class PlayerLoader extends ClassLoader {

private Hashtable Classes = new Hashtable();

public Class loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

try {

Class newClass = (Class)Classes.get(name);

if (newClass == null) {  // еще не определен

try {               // проверить, не является

// ли системным классом

newClass = findSystemClass(name);

if (newClass != null)

return newClass;

} catch (ClassNotFoundException e) {

;              // продолжить поиск

}

// класс не найден – его необходимо загрузить

byte[] buf = bytesForClass(name);

newClass = defineClass(buf, 0, buf.length); Classes.put(name, newClass);

}

if (resolve)

resolveClass (newClass);

return newClass;

} catch (IOException e) {

throw new ClassNotFoundException(e.toString());

}

}

// … bytesForClass() и все прочие методы …

}

Любая реализация loadClass должна загружать класс лишь в том случае, если это не было сделано ранее, поэтому метод содержит хеш-таблицу загруженных классов. Если класс уже присутствует в ней, то возвращается соответствующий  объект Class. В противном случае loadClass сначала проверяет, можно ли найти класс в локальной системе, для чего вызывает метод find SystemClass класса ClassLoader; этот метод осуществляет поиск не только среди системных классов (находящихся в пакетах java), но и в пути, заданном для классов. Если при этом будет найден нужный класс, то после выполнения загрузки возвращается соответствующий  ему объект Class.

Если поиск в обоих случаях заканчивается безрезультатно, необходимо прочитать байт-

код класса, для чего служит метод bytesForClass:

protected byte[] bytesForClass(String name)

throws IOException, ClassNotFoundException

{

FileInputStream in = streamFor(name);

int length = in.available();   // получить количество байтов

if (length == 0)

throw new ClassNotFoundException(name);

byte[] buf = new byte[length];

in.read(buf);                  // прочитать байт-код

return buf;

}

В нем использован метод streamFor (не приводится) для получения потока

FileInputStream, содержащего байт-код класса. Затем мы создаем буфер нужного размера,

считываем весь байт-код и возвращаем буфер.

Когда loadClass получает байт-код и вызывается метод defineClass класса ClassLoader, в качестве параметров этот метод получает массив byte, начальное смещение и количество байтов — в указанной части массива должен находиться байт-код класса. В данном случае байт-код занимает весь массив. Когда определение класса завершается, он добавляется в хеш-таблицу Classes, чтобы предотвратить его повторную загрузку.

После успешной загрузки класса метод loadClass возвращает новый объект Class.

Невозможно выгрузить класс, когда он перестает быть нужным. Вместо этого вы просто прекращаете его использование, чтобы класс-загрузчик мог быть уничтожен сборщиком мусора.

Чтобы получить объект-загрузчик  для заданного объекта Class, следует вызвать его метод getClassLoader. Процесс загрузки классов был рассмотрен выше. Если у данного класса отсутствует класс-загрузчик, метод возвращает null.

Класс-загрузчик применяется лишь на первой стадии подготовки класса к работе. Всего же существует три стадии:

1. Загрузка: получение байт-кода с реализацией класса.

2. Компоновка: поиск супертипов класса и загрузка их в случае необходимости.

3. Инициализация:  присвоение начальных значений статическим полям класса и выполнение их инициализаторов,  а также всех статических блоков.

Упражнение 13.2

Реализуйте классы Game и Player для какой-нибудь простой игры — например, “крестики-

нолики”. Проведите оценку различных реализаций Player по данным нескольких запусков.

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

  • Комментарии