Рефлексия: решение "нетрадиционных" задач


Я не буду слишком сильно все разжевывать, т.к. сама идея рефлексии не имеет ничего хитрого, просто об этом механизме часто забывают. Человек, писавший что-то сложнее копипаста примеров, должен догадаться моментально по паре объяснений и примеров.

Рефлексия - это, говоря простыми словами, способ обращаться программе к собственному коду. Само понятие рефлексии в программировании сейчас используется в контексте "управляемого кода" - кода, который выполняется в интерпретаторе, а не напрямую, как в компилируемых языках.

Рефлексией пользуются относительно не часто, но такой способ проектирования имеет большие преимущества по сравнению со стандартными паттернами проектирования. Рефлексия позволяет делать код крайне модульным без перекомпиляции, что имеет отдельное значение в "управляемом коде", в котором нету возможности (как в C/C++/ASM и т.д.) получить прямой доступ к любому месту в памяти и переписать.

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

Несколько простых примеров использования такого подхода на JAVA и PHP.

------ PHP ------

<?   
$processor = new RequestProcessor();
$funcname = RequestProcessor::FUNC_PREFIX.$_GET['action'];
if(method_exists($processor, $funcname)) {
echo $processor->$funcname($xpath, $name[0]);
}
// в PHP подгружать классы динамически можно через встроенные механизмы:
// http://php.net/manual/en/language.oop5.autoload.php
?>


------ JAVA ------

Class clazz = null;
try {
clazz = Class.forName("some.package.TheActionsClass");
Object obj = clazz.newInstance();
Method actionMethod = clazz.getDeclaredMethod(anAction);
String result = (String) actionMethod.invoke(obj);
System.out.println("result: "+result);
} catch (
ClassNotFoundException | InstantiationException |
IllegalAccessException | NoSuchMethodException |
InvocationTargetException e)
{
e.printStackTrace();
}
// в JAVA загружаемые классы должны входить в classpath при старте приложения

Нельзя говорить о рефлексии в Java, не упомянув об аннотациях.
В Java существует такой механизм как аннотации (java annotations), позволяющий параметризировать код отдельно от его содержания. На этом построены многие технологии (JPA, например), значительно упрощающие разработку.
Аннотация может быть поставлена к классу, полю и методу и существует для того, чтобы ставить собственные описания к коду и использовать его любым удобным способом без дополнительных конструкций.
Простой пример:
добавляем к любому методу в коде аннотацию @OnTimeTick, далее с помошью механизмов рефлексии можно получить все такие методы и, соответственно, вызывать их по срабатыванию некоторого таймера.


------ JAVA ------

clazz = Class.forName("some.package.SomeExampleClass");
for (Method method : clazz.getDeclaredMethods()) {
    if (method.getAnnotation(OnTimerTick.class) != null) {
        method.invoke(this);
    }
}

Далее: как можно этот механизм использовать для "нетрадиционных" задач.
Java обладает одним свойством, которое иногда очень сильно раздражает разработчиков - она легко декомпилируется. Чаще всего от этого спасаются обфускацией кода, что не решает основную задачу - декомпиляцию, но делает анализ кода демотивирующи сложным, но все же посильным в разумные сроки.

Сама структура организации скомпилированного кода (байт-кода) достаточно проста и уже сама по себе может дать какую-то информацию о приложении без декомпиляции - будь то jar (zip папки, по сути) или просто файлы классов. Более того, даже код, прошедший обфускацию, содержит в скомпилированном виде паттерны, которые могут быть использованы, например, антивирусами.

Чем тут может помочь рефлексия? Рефлексия в java позволяет создавать собственный загрузчик классов, где на входе мы получаем бинарный поток данных, а на выходе должны предоставить экземпляр искомого класса. Таким образом, собственно, байт-код может быть закриптован любым способом, на который только хватит фантазии.

В данной папке приведен самый простой вариант такого загрузчика. Код состоит из 2-х частей:

  • криптограф - класс, который шифрует байт-код и упаковывает в файл дополнительную информацию, необходимую для загрузки класса.
  • загрузчик - класс, который загружает закриптованный файл, расшифровывает и запускает метод main(). Таким образом, зашифрованный класс будет запускаться точно также, как и без шифрования.

В качестве шифрования сугубо для примера используется BASE64. Такая пара криптограф-загрузчик позволяет зашифровать и загружать любой скомпилированный класс-файл. Несложная доработка позволит, таким образом, паковать в единый файл целую структуру классов.

Где это может быть полезно?
При пересылке кода по открытым сетям, например, если этот код является догружаемым функционалом бота и должен быть размещен на открытых ресурсах, на pastebin, например.

Исходники: sources/KostaPC/reflection/java_reflection_encoder


______________________________
KostaPC
2013

Inception E-Zine