Java Language
Загрузчики классов
Поиск…
замечания
Класс loader - это класс, основной целью которого является опосредование местоположения и загрузки классов, используемых приложением. Погрузчик классов также может находить и загружать ресурсы .
Стандартные классы classloader могут загружать классы и ресурсы из каталогов в файловой системе и из JAR и ZIP-файлов. Они также могут загружать и кэшировать файлы JAR и ZIP с удаленного сервера.
Классовые загрузчики обычно закодированы, так что JVM будет пытаться загружать классы из стандартных библиотек классов, предпочитая источники, предоставленные приложением. Пользовательские загрузчики классов позволяют программисту изменять это. Также можно делать такие вещи, как дешифрование файлов байт-кода и модификация байт-кода.
Создание и использование загрузчика классов
Этот базовый пример показывает, как приложение может создавать экземпляр класса loader и использовать его для динамической загрузки класса.
URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");
Созданный в этом примере загрузчик классов будет иметь загрузчик по умолчанию как родительский, и сначала попытается найти любой класс в родительском загрузчике классов, прежде чем смотреть в «extra.jar». Если запрошенный класс уже загружен, вызов findClass
вернет ссылку на ранее загруженный класс.
findClass
может завершиться неудачей различными способами. Наиболее распространенными являются:
- Если названный класс не может быть найден, вызов с throw
ClassNotFoundException
. - Если названный класс зависит от какого-либо другого класса, который не может быть найден, вызов вызовет
NoClassDefFoundError
.
Внедрение пользовательского классаLoader
Каждый пользовательский загрузчик должен прямо или косвенно расширить класс java.lang.ClassLoader
. Основными точками расширения являются следующие методы:
-
findClass(String)
- перегружает этот метод, если ваш загрузчик классов следует стандартной модели делегирования для загрузки классов. -
loadClass(String, boolean)
- перегружает этот метод для реализации альтернативной модели делегирования. -
findResource
иfindResources
- перегружать эти методы для настройки загрузки ресурсов.
Методы defineClass
которые отвечают за фактическую загрузку класса из массива байтов, являются final
чтобы предотвратить перегрузку. Перед вызовом defineClass
.
Вот простой, который загружает определенный класс из массива байтов:
public class ByteArrayClassLoader extends ClassLoader {
private String classname;
private byte[] classfile;
public ByteArrayClassLoader(String classname, byte[] classfile) {
this.classname = classname;
this.classfile = classfile.clone();
}
@Override
protected Class findClass(String classname) throws ClassNotFoundException {
if (classname.equals(this.classname)) {
return defineClass(classname, classfile, 0, classfile.length);
} else {
throw new ClassNotFoundException(classname);
}
}
}
Поскольку мы только переопределили метод findClass
, этот пользовательский загрузчик классов будет вести себя следующим образом при вызове loadClass
.
- Метод
loadClass
findLoadedClass
вызываетfindLoadedClass
чтобы узнать, был ли класс с этим именем уже загружен этим загрузчиком классов. Если это удастся, результирующий объектClass
возвращается запрашивающему. - Метод
loadClass
затем передает родительскому загрузчику классов, вызывая его вызовloadClass
. Если родитель может обработать запрос, он вернет объектClass
который затем возвращается запрашивающему. - Если родительский загрузчик классов не может загрузить класс,
findClass
затем вызывает наш метод overridefindClass
, передавая имя загружаемого класса. - Если запрашиваемое имя соответствует
this.classname
, мы вызываемdefineClass
для загрузки фактического класса изthis.classfile
байтовthis.classfile
. Затем возвращается возвращаемый объектClass
. - Если имя не
ClassNotFoundException
, мы бросаемClassNotFoundException
.
Загрузка внешнего файла .class
Чтобы загрузить класс, мы сначала должны его определить. Класс определяется ClassLoader
. Есть только одна проблема, Oracle не написала код ClassLoader
с этой доступной функцией. Чтобы определить класс, нам нужно получить доступ к методу с именем defineClass()
который является приватным методом класса ClassLoader
.
Чтобы получить доступ к нему, мы создадим новый класс ByteClassLoader
и распространим его на ClassLoader
. Теперь, когда мы расширили наш класс до ClassLoader
, мы можем получить доступ к приватным методам ClassLoader
. Чтобы сделать defineClass()
доступным, мы создадим новый метод, который будет действовать как зеркало для частного defineClass()
. Для вызова частного метода нам потребуется имя класса, name
, класс байт, classBytes
, смещенные первый байт, который будет 0
, поскольку classBytes
данные 'начинается в classBytes[0]
, и смещение последнего байта, который будет classBytes.lenght
потому что он представляет собой размер данных, который будет последним смещением.
public class ByteClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] classBytes) {
return defineClass(name, classBytes, 0, classBytes.length);
}
}
Теперь у нас есть общедоступный defineClass()
. Его можно вызвать, передав имя класса и байты класса в качестве аргументов.
Допустим, у нас есть класс с именем MyClass
в пакете stackoverflow
...
Для вызова метода нам нужны байты класса, поэтому мы создаем объект Path
представляющий Path
нашего класса, используя метод Paths.get()
и передавая путь двоичного класса в качестве аргумента. Теперь мы можем получить байты класса с Files.readAllBytes(path)
. Поэтому мы создаем экземпляр ByteClassLoader
и используем метод, который мы создали, defineClass()
. У нас уже есть байты класса, но для вызова нашего метода нам также нужно имя класса, которое дается именем пакета (точка) канонического имени класса, в данном случае stackoverflow.MyClass
.
Path path = Paths.get("MyClass.class");
ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);
Примечание . Метод defineClass()
возвращает объект Class<?>
. Вы можете сохранить его, если хотите.
Чтобы загрузить класс, мы просто вызываем loadClass()
и передаем имя класса. Этот метод может генерировать ClassNotFoundException
поэтому нам нужно использовать блок cath try
try{
loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
e.printStackTrace();
}