Эта статья состоит из 3 — х частей:
1. Описание возможностей отражения
2. Немного о ClassLoader'е
3. Пишем (практика) загрузчик плагинов для разных проектов
Так как java код сначал «превращается» в промежуточный байткод, а только потом
в jre на конкретной машине с помощью JIT(Just In Time)-компиляции превращается в
настоящий код или интерпртируется интерпретатором java, поэтому появилась технология «Отражение» — способность программы изменять
саму себя во время работы.
Давайте подробно рассмотрим эти возможности.
Чтобы не быть голословным я накидал маленький класс.
Особого смысла не ищите в коде, просто наглядное пособие.
Чтобы воздействовать с классом нам нужен (вы не поверите) объект типа Class:
Получить его можно несколькими способами:
1. Если класс заранее известен, то можно воспользоваться конструкцией Color.class (пример)
2. Через экземпляр объекта: AnObject.getClass()
3. Пользуясь объектом ClassLoader или Class.forName() (-> 2 часть)
Class следует записывать как Class<?>
Потому что скобки дают несколько возможностей:
Например указать класс напрямую:
Class ColorClass= Color.class;
или Указать что класс наследник другого
Class<? extends Object> ColorClass = Color.class;//(любой класс — подкласс Object)
Теперь давайте рассмотрим возможности Class:
Сначала начнём с простого и создадим экземпляр:
Для этого нам нужно
1. Получить конструктор по его параметрам
2. Вызвать его и присвоить результат новой переменной.
Этот код порождает целую пачку возможных ошибок:
>InstantiationException — невозможность создать экземпляр:
Например класс абстрактный или интерфейс и многое другое.
>IllegalAcessException — нет прав доступа,
например конструктор приватный, но мы с этим справимся попозже.
>IllegalArgumentException — неправильные аргументы в методе .newInstance()
>InvocationTargetException — исключение внутри конструктора.
>>Ну и не стоит забывать о ClassCastException, будьте бдительны.
Теперь давайте попробуем вызвать наш публичный метод blend(не статичный) и newInstance (статичный).
-----Замечания (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 Вызовем его как обычно.
Не забываем добавить те же проверки на исключения, запускаем. 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 (плохие аргументы, нет доступа)
========ЧАСТЬ 2=========
==Немного о ClassLoader ==
Итак ClassLoader служит для загрузки классов. Когда вы запускаете java-программу
вы сразу загружаете ClassLoader'ом главный класс. Каждый класс «помнит» ClassLoader который
его загрузил (Class.getClassLoader(); ObjectA.getClassLoader();)
Когда ему(экземпляру класса) понадобиться обратиться к другим классам он обратиться к этому ClassLoader'у
и загрузит их. Поэтому если вы не касаетесь этой области, то все классы будет загружать системный ClassLoader.
Также если ClassLoader уже загрузил класс, то он вернёт байткод без повторной загрузки.
Давайте попробуем с помощью ClassLoader'а загрузить классы.
Для начала нам нужно получить ссылку на системный ClassLoader
Или на ClassLoader нашего класса.
Теперь давайте загрузим класс.
мы получили тот же класс что и раньше, но это далеко не полный список возможностей 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)
Перед тем как использовать данный класс его нужно resolve'ить(аналогично loadClass(String name, boolean resolve))
Подробнее о ClassLoader'е можно прочитать здесь: piarmedia.ru/?page_id=9
=====Часть 3=====
====Практика====
Пишем модуль плагинов/модов
Как ясно из части 2 есть 2 основных способы гибкой динамической загрузки класса:
1. Собственный ClassLoader, но это сложный путь.
---Так как загруженные классы будут обращаться к вам же за загрузкой других классов в т ч и системных.
2. Получение метода defineClass и resolve через reflection и их использование.
Я поддерживаю второй вариант им мы и будем пользоваться.
За основу возьмём такой класс:
IPlugin — интерфейс главного класса плагина.
Например такой примитивный:
Давайте начнём: в конструкторе мы будем получать ссылку на защищённые методы defineclass и resolveclass:
Теперь пишем реализацию loadPlugins()
Внутри try:
И на этом всё, Спасибо за просмотр.
Если у вас возникли какие-то вопросы — задавайте.
1. Описание возможностей отражения
2. Немного о ClassLoader'е
3. Пишем (практика) загрузчик плагинов для разных проектов
Часть 1
Так как 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)
{return c.hashCode();}
//Декоративно
public String toString()
{return String.format("r %d g %d b % d", r,g,b);}
}
Чтобы воздействовать с классом нам нужен (вы не поверите) объект типа Class:
Получить его можно несколькими способами:
1. Если класс заранее известен, то можно воспользоваться конструкцией Color.class (пример)
2. Через экземпляр объекта: AnObject.getClass()
3. Пользуясь объектом ClassLoader или Class.forName() (-> 2 часть)
Class следует записывать как Class<?>
Потому что скобки дают несколько возможностей:
Например указать класс напрямую:
Class ColorClass= Color.class;
или Указать что класс наследник другого
Class<? extends Object> ColorClass = Color.class;//(любой класс — подкласс Object)
Теперь давайте рассмотрим возможности Class:
Сначала начнём с простого и создадим экземпляр:
Для этого нам нужно
1. Получить конструктор по его параметрам
2. Вызвать его и присвоить результат новой переменной.
Class<? extends Object> 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, будьте бдительны.
Теперь давайте попробуем вызвать наш публичный метод 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 (плохие аргументы, нет доступа)
========ЧАСТЬ 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 {
Class<?> ColorClass = c.loadClass("Color");
/* ***** */
}
catch (ClassNotFoundException e)
{e.printStackTrace();}
мы получили тот же класс что и раньше, но это далеко не полный список возможностей 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)
Перед тем как использовать данный класс его нужно 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<IPlugin> loadPlugins(File[] list)//Загружаем плагины из списка
{
}
}
IPlugin — интерфейс главного класса плагина.
Например такой примитивный:
public interface IPlugin
{
public void Init();
}
Давайте начнём: в конструкторе мы будем получать ссылку на защищённые методы defineclass и resolveclass:
private Method resolve, define;
public PluginLoader() throws NoSuchMethodException, SecurityException
{
Class<ClassLoader> c=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<IPlugin> loadPlugins(File[] list)
{
ArrayList<IPlugin> res = new ArrayList<IPlugin>();//Результат
FileInputStream reader;//Поток для чтения
ClassLoader c = ClassLoader.getSystemClassLoader();//Системный ClassLoader
Class<?> cl; //Загруженный класс
Object instance; //Его экземпляр
for (File f : list)
{
try
{
///////////*****//////////
}
catch (Exception e)
{
PrintErorInfo(e);//Отправляем ошибку в лог и идём дальше
}
}
return res;//Возвращаем всё что загрузили
}
Внутри try:
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)//Если это плагин, то добавляем в результат
{
res.add((IPlugin)instance);
}
И на этом всё, Спасибо за просмотр.
Если у вас возникли какие-то вопросы — задавайте.