Java plugin load class

java — Отражение; Динамическая загрузка классов

Так как java код сначал «превращается» в промежуточный байткод, а только потом
в jre на конкретной машине с помощью JIT(Just In Time)-компиляции превращается в
настоящий код или интерпртируется интерпретатором java, поэтому появилась технология «Отражение» — способность программы изменять
саму себя во время работы.

Давайте подробно рассмотрим эти возможности.

Чтобы не быть голословным я накидал маленький класс.
Особого смысла не ищите в коде, просто наглядное пособие.

public class Color < public byte r,g,b; //конструктор public Color(byte r_,byte g_, byte b_) < r=r_; g=g_; b=b_; >//публичный метод public Color Blend(Color c) < return new Color(Average(this.r, c.r), Average(this.g, c.g), Average(this.b, c.b)); >//публичный и статичный метод //что-то вроде clone() public static Color newInstance(Color c) < return new Color(c.r,c.g,c.b); >//приватный метод private byte Average(byte a, byte b) < return (byte) ((a/2)+(b/2)); >//приватный статичный метод private static int getSecret(Color c) //Декоративно public String toString() > 

Чтобы воздействовать с классом нам нужен (вы не поверите) объект типа Class:

Получить его можно несколькими способами:

1. Если класс заранее известен, то можно воспользоваться конструкцией Color.class (пример)
2. Через экземпляр объекта: AnObject.getClass()
3. Пользуясь объектом ClassLoader или Class.forName() (-> 2 часть)

Class следует записывать как Class
Потому что скобки дают несколько возможностей:
Например указать класс напрямую:
Class ColorClass= Color.class;
или Указать что класс наследник другого
Class ColorClass = Color.class;//(любой класс — подкласс Object)

Теперь давайте рассмотрим возможности Class:

Сначала начнём с простого и создадим экземпляр:

Для этого нам нужно
1. Получить конструктор по его параметрам
2. Вызвать его и присвоить результат новой переменной.

Class ColorClass = Color.class;//получаем класс как было сказано ранее Color c=(Color)getConstructor(byte.class, byte.class, byte.class)//получаем конструктор с задаными параметрами (byte, byte, byte) .newInstance((byte)0,(byte)1,(byte)2)//вызываем, результат типа Object. Приводим. 

Этот код порождает целую пачку возможных ошибок:
>InstantiationException — невозможность создать экземпляр:
Например класс абстрактный или интерфейс и многое другое.
>IllegalAcessException — нет прав доступа,
например конструктор приватный, но мы с этим справимся попозже.
>IllegalArgumentException — неправильные аргументы в методе .newInstance()
>InvocationTargetException — исключение внутри конструктора.
>>Ну и не стоит забывать о ClassCastException, будьте бдительны.

Читайте также:  Java reverse byte array

Теперь давайте попробуем вызвать наш публичный метод blend(не статичный) и newInstance (статичный).

 ColorClass.getDeclaredMethod("Blend", ColorClass)//Получаем метод по именем(не забываем про регистр) и пареметрам .invoke(c, c);// вызываем первый c - экземпляр, второй - параметр. //со static всё приблизительно также: ColorClass.getDeclaredMethod("newInsctance", ColorClass)//метод по имени .invoke(null, c);//так как метод статичный можно написать null как объект 

——Замечания (1)——
Для некоторых вещей понадобиться импортировать java.lang.reflect.*;

Хочу отметить, что необязательно для того чтобы вызвать метод/конструктор каждый раз его получать.
Можно его сохранить в экземпляра класса Method(java.lang.reflect.Method) и от него уже вызывать invoke
Так же в динамической загрузке классов возможно следует создать интерфейс и приводить загруженные классы к нему, чтобы каждый раз не делать getDeclaredMethod()…
Ещё следует сказать о том, что с помощью Class.getDeclaredMethods() можно получить массив всех методов
и их отрабатывать используя
Method.getName() —String
Method.getParameterTypes() —Class[]
Method.getReturnType() —Class

— Теперь давайте сделаем что-нибудь интересное: например вызовем private методы Average и getSecret.
(с конструктором всё также, я не буду повторятся для экономии Вашего времени)

1 Для начала получим два метода (как в прошлый раз).
2 Сами себе разрешим доступ.
3 Вызовем его как обычно.

///1 Method secret, average; average = ColorClass.getDeclaredMethod("Average", byte.class, byte.class); secret = ColorClass.getDeclaredMethod("getSecret", ColorClass); ///2 average.setAccessible(true); secret.setAccessible(true); ///3 int CSecret = (int) secret.invoke(null, c); byte b = (byte) average.invoke(c, (byte)10, (byte)6); System.out.println(CSecret); System.out.println(b); 

Не забываем добавить те же проверки на исключения, запускаем. OK.
>2100855933
>8

Теперь давайте расскажу про поля… с ними ситуация похожая:
Class.getDeclaredField(String name) возвращает Field
бросает:
NoSuchFieldException — Нет такого поля
SecurityException — Нет прав

SomeClass.getDeclaredFields() — массив Field[], все поля класса.

Field.setAcessible(true) разрешает доступ.

Field.getName() — имя. (для SomeClass.getDeclaredFields())
Field.getType() —Class тип. (для SomeClass.getDeclaredFields())

Field.get(Object e) —Object получение значения по экземпляру(как мы знаем для статик элементов можно использовать null)
throws IllegalArgumentException, IllegalAccessException (плохие аргументы, нет доступа)
Field.set(Object e, Object param) —void установление значения по экземпляру(как мы знаем для статик элементов можно использовать null)
throws IllegalArgumentException, IllegalAccessException (плохие аргументы, нет доступа)

Читайте также:  Java get annotations from class

========ЧАСТЬ 2=========
==Немного о ClassLoader ==

Итак ClassLoader служит для загрузки классов. Когда вы запускаете java-программу
вы сразу загружаете ClassLoader’ом главный класс. Каждый класс «помнит» ClassLoader который
его загрузил (Class.getClassLoader(); ObjectA.getClassLoader();)
Когда ему(экземпляру класса) понадобиться обратиться к другим классам он обратиться к этому ClassLoader’у
и загрузит их. Поэтому если вы не касаетесь этой области, то все классы будет загружать системный ClassLoader.
Также если ClassLoader уже загрузил класс, то он вернёт байткод без повторной загрузки.
Давайте попробуем с помощью ClassLoader’а загрузить классы.

Для начала нам нужно получить ссылку на системный ClassLoader
Или на ClassLoader нашего класса.

ClassLoader c = ClassLoader.getSystemClassLoader(); /////ИЛИ ClassLoader c = this.getClassLoader(); 
try < ClassColorClass = c.loadClass("Color"); /* ***** */ > catch (ClassNotFoundException e)

мы получили тот же класс что и раньше, но это далеко не полный список возможностей ClassLoader’а.

loadClass вызывает защищённый
protected synchronized Class loadClass(String name, boolean resolve=false) [ Этот метод также используется Class.forName О нём чуть ниже]
параметр resolve определяет будут ли все классы на которые он ссылается gjlгружены прямо сейчас или отложены до необходимости.

Давайте рассмотрим ещё 1 способ загрузки класса использую Class.forname()
public static Class forName(String name, boolean initialize, ClassLoader loader);
name — имя класса.
initialize — определяет поведение static области класса (выполняемые при его загрузки) static
-если true, то инициализация будет выполнена немедленно, иначе будет отложена до первого обращения к классу.
loader — класслоадер.

Как может показаться это равносильно loader.loadClass(name), но всё-таки следует использовать Class.forName
т к это метод делает дополнительные манипуляции с классом, а также кэширует его,
обеспечивая адекватную работу даже при неаккуратной загрузчика loader.

И protected способ ClassLoader’а:

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
name — ожидаемое имя класса, или null если неизвестно.
b — байткод класса, например считанный с FileInputStream
off — offset чтения буфера
len — длина для чтения
бросает:
ClassFormatError — Если данные — не валидный класс.
SecurityException — Если была попытка добавить класс в пакет который имеет особый сертификат безопасности ProtectionDomain(это отдельная тема), или в системный пакет java.*.
Чувствителный к ProtectionDomain метод protected final Class defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)

Читайте также:  File filters in java

Перед тем как использовать данный класс его нужно resolve’ить(аналогично loadClass(String name, boolean resolve))

Подробнее о ClassLoader’е можно прочитать здесь: piarmedia.ru/?page_id=9
=====Часть 3=====
====Практика====
Пишем модуль плагинов/модов

Как ясно из части 2 есть 2 основных способы гибкой динамической загрузки класса:
1. Собственный ClassLoader, но это сложный путь.
—Так как загруженные классы будут обращаться к вам же за загрузкой других классов в т ч и системных.
2. Получение метода defineClass и resolve через reflection и их использование.

Я поддерживаю второй вариант им мы и будем пользоваться.
За основу возьмём такой класс:

import java.lang.reflect.*; import java.io.*; import java.util.ArrayList; public class PluginLoader < public PluginLoader()//Инициализация < >public void PrintErorInfo(Exception e)//Для логгирования ошибок < System.out.print("FAIL; file skipped"); e.printStackTrace(); >public ArrayList loadPlugins(File[] list)//Загружаем плагины из списка < >> 

Давайте начнём: в конструкторе мы будем получать ссылку на защищённые методы defineclass и resolveclass:

private Method resolve, define; public PluginLoader() throws NoSuchMethodException, SecurityException < Classc=ClassLoader.class; define = c.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); resolve = c.getDeclaredMethod("resolveClass", Class.class); define.setAccessible(true);//методы - protected resolve.setAccessible(true); > 

Теперь пишем реализацию loadPlugins()

public ArrayList loadPlugins(File[] list) < ArrayListres = new ArrayList();//Результат FileInputStream reader;//Поток для чтения ClassLoader c = ClassLoader.getSystemClassLoader();//Системный ClassLoader Class cl; //Загруженный класс Object instance; //Его экземпляр for (File f : list) < try < ///////////*****////////// >catch (Exception e) < PrintErorInfo(e);//Отправляем ошибку в лог и идём дальше >> return res;//Возвращаем всё что загрузили > 
 reader = new FileInputStream(f);//Инициализируем byte[] b = new byte[reader.available()];//Читаём всё. reader.read(b); reader.close(); cl = (Class) define.invoke(c, null, b, 0, b.length);//Вызываем названный метод defineClass. От лица //Системного класслоадера, имя мы не знаем, читаем полностью от 0 до b.length resolve.invoke(c, cl);/// c.resolveClass(cl) instance = cl.newInstance();//Вызываем конструктор без аргументов if (instance instanceof IPlugin)//Если это плагин, то добавляем в результат

Источник

Оцените статью