Рефлексия кода, reflection
Рефлексия (от reflexio — обращение назад) — это механизм исследования данных о программе во время её выполнения. Рефлексия в Java осуществляется с помощью Java Reflection API, состоящий из классов пакетов java.lang и java.lang.reflect. В информатике рефлексия означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.
Java Reflection API позволяет получать информацию о конструкторах, методах и полях классов и выполнять следующие операции над полями и методами объекта/класса :
- определение класса объекта;
- получение информации о полях, методах, конструкторах и суперклассах;
- получение информации о модификаторах полей и методов;
- создание экземпляра класса, имя которого неизвестно до момента выполнения программы;
- определение и изменение значений свойств объекта/класса;
- вызов методов объекта/класса.
Примечание : в тексте используется объект/класс. При работе с объектом (реализацией класса) можно обращаться к полям и методам класса напрямую, если они доступны (не private). При работе с классом можно обращаться к методам класса с использованием Java Reflection API. Но класс необходимо получить из объекта.
Определение свойств класса
В работающем приложении для получения класса необходимо использовать метод forName (String className). Следующий код демонстрирует возможность создания класса без использования и с использованием Reflection :
// Без использования Reflection Foo foo = new Foo(); // С использованием Reflection Class foo = Class.forName("Foo"); // Загрузка JDBC-драйвера Class.forName("com.mysql.jdbc.Driver");
Метод класса forName(className) часто используется для загрузки JDBC-драйвера.
Методом getName() объекта Class можно получить наименование класса, включающего пакет (package) :
Class aclass = foo.getClass(); System.out.println (aclass.getName());
Для получения значения модификатора класса используется метод getModifiers(). Класс java.lang.reflect.Modifier содержит статические методы, возвращающие логическое значения проверки модификатора класса :
Class cls = foo.getClass(); int mods = cls.getModifiers(); if (Modifier.isPublic (mods)) < System.out.println("public"); >if (Modifier.isAbstract(mods)) < System.out.println("abstract");>if (Modifier.isFinal (mods))
Для получения суперкласса рефлексированного объекта (класса) необходимо использовать метод getSuperclass() :
Class cls = foo.getClass(); Class superCls = cls.getSuperClass();
Поскольку в Java отсутствует множественное наследование, то для получения всех предков следует рекурсивно вызвать метод getSuperclass() в цикле, пока не будет достигнут Object, являющийся родителем всех классов. Object не имеет родителей, поэтому вызов его метода getSuperclass() вернет null.
Определение интерфейсов и конструкторов класса
Для получения в режиме run-time списка реализующих классом интерфейсов, необходимо получить Class и использовать его метод getInterfaces(). В следующем примере извлекается список интерфейсов класса ArrayList :
Class cls = ArrayList.class; Class[] ifs = cls.getInterfaces(); System.out.println(«List of interfaces\n»); for(Class ifc : ifs)
Чтобы IDE (Eclipse) не предупреждала о необходимости определения типа класса
Class is a raw type. References to generic type Class should be parameterized
в коде были использованы generic’и. В консоль выводятся следующие интерфейсы, реализуемые классом ArrayList :
List of interfaces java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable
Метод класса getConstructors() позволяет получить массив открытых конструкторов типа java.lang.reflect.Constructor. После этого, можно извлекать информацию о типах параметров конструктора и генерируемых исключениях :
Class cls = obj.getClass(); Constructor[] constructors = cls.getConstructors(); for (Constructor constructor : constructors) < Class[] params = constructor.getParameterTypes(); for (Class param : params) < System.out.println(param.getName()); >>
Определение полей класса
Метод getFields() объекта Class возвращает массив открытых полей типа java.lang.reflect.Field, которые могут быть определены не только в данном классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Field позволяет получить имя поля, тип и модификаторы :
Class cls = obj.getClass(); Field[] fields = cls.getFields(); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); >
Если известно наименование поля, то можно получить о нем информацию с помощью метода getField() объекта Class.
Class cls = obj.getClass(); Field fld = cls.getField("fieldName");
Методы getField() и getFields() возвращают только открытые члены данных класса. Чтобы получить все поля класса, включая закрытые и защищенные, необходимо использовать методы getDeclaredField() и getDeclaredFields(). Данные методы работают точно так же, как и их аналоги getField() и getFields().
Определение значений полей класса
Класс Field содержит специализированные методы для получения значений примитивных типов: getInt(), getFloat(), getByte() и др. Для установки значения поля, используется метод set(). Для примитивных типов имеются методы setInt(), setFloat(), setByte() и др.
Class cls = obj.getClass(); Field field = cls.getField("fieldName"); String value = (String) field.get(obj); field.set(obj, "New value");
Ниже приведен пример изменения значения закрытого поля класса в режиме run-time.
Определение методов класса
Метод getMethods() объекта Class возвращает массив открытых методов типа java.lang.reflect.Method. Эти методы могут быть определены не только в классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Method позволяет получить имя метода, тип возвращаемого им значения, типы параметров метода, модификаторы и генерируемые исключения.
Class cls = obj.getClass(); Method[] methods = cls.getMethods(); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class paramType : params) < System.out.print(" " + paramType.getName()); >System.out.println(); >
Если известно имя метода и типы его параметров, то можно получить отдельный метод класса :
Class cls = obj.getClass(); Class[] params = new Class[] ; Method method = cls.getMethod("methodName", params);
Пример изменения значения закрытого поля класса
Чтобы изменить значение закрытого (private) поля класса необходимо получить это поле методом getDeclaredField () и вызвать метод setAccessible (true) объекта Field, чтобы открыть доступ к полю. После этого значение закрытого поля можно изменять, если оно не final. В следующем примере определен внутренний класс PrivateFinalFields с набором закрытых полей; одно из полей final. При создании объекта класса поля инициализируются. В методе main примера поочередно в закрытые поля вносятся изменения и свойства объекта выводятся в консоль.
import java.lang.reflect.Field; class PrivateFinalFields < private int i = 1; private final String s = "String S"; private String s2 = "String S2"; public String toString() < return "i = " + i + ", " + s + ", " + s2; >> public class ModifyngPrivateFields < public static void main(String[] args) throws Exception < PrivateFinalFields pf = new PrivateFinalFields(); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 47); System.out.println("1. " + pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); f.set(pf, "MODIFY S"); System.out.println("2. " + pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); f.set(pf, "MODIFY S2"); f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); f.setInt(pf, 35); System.out.println("3. " + pf); >>
В результате выполнения примера в консоль будут выведены следующие сообщения :
1. i = 47, String S, String S2 2. i = 47, String S, String S2 3. i = 35, String S, MODIFY S2
Из приведённого примера видно что поля private можно изменять. Для этого необходимо получить объект типа java.lang.reflect.Field с помощью метода getDeclaredField (), вызвать его метод setAccessible (true) и с помощью метода set () установить требуемое значение поля. Необходимо иметь в виду, что наличие модификатора final в закрытом текстовом поле не вызывает исключений при изменении значений, а само значение поля остаётся прежним, т.е. final поля остаются неизменные. Если не вызвать метод открытия доступа к полю setAccessible (true), то будет вызвано исключение java.lang.IllegalAccessException.
Пример вызова метода, invoke
Java Reflection Api позволяет вызвать метод класса. Рассмотрим пример, в котором определим класс Reflect, включающий поля и методы управления ими. В режиме run-time с помощью метода данного класса будем изменять значения полей и распечатывать их.
Листинг класса Reflect
Класс Reflect включает два закрытых поля (id, name) и методы управления их значениями set/get. Дополнительно в класс включим метод setData, который будем вызывать для изменения значений полей, и метод toString для печати их значений.
class Reflect < private String name; private int id; Reflect() < name = "Test"; >public int getId() < return id; >public void setId(int id) < this.id = id; >String getName() < return name; >public void setName(String name) < this.name = name; >public void setData(final int id, String name) < this.id = id; this.name = name; >@Override public String toString() < return "Reflect [ id : " + id + ", name : " + name + "]"; >>
Для тестирования объекта типа Reflect с помощью Java Reflection Api создадим класс ReflectionTest. В этот класс включим две процедуры getClassFields и getClassMethods, которые в режиме run-time распечатают всю информацию (описание полей и методов) о классе. Методы получают класс в качестве параметра. В процедурах сначала определяются массивы полей и методы; после этого их параметры распечатываются :
private void getClassFields(Class cls) < Field[] fields = cls.getDeclaredFields(); System.out.println("Class fields"); for (Field field : fields) < Classfld = field.getType(); System.out.println("Class name : " + field.getName()); System.out.println("Class type : " + fld.getName()); > > private void getClassMethods(Class cls) < Method[] methods = cls.getDeclaredMethods(); System.out.println("Class methods"); for (Method method : methods) < System.out.println("Method name : " + method.getName()); System.out.println("Return type : " + method.getReturnType().getName()); Class[] params = method.getParameterTypes(); System.out.print("Parameters : "); for (Class param : params) System.out.print(" " + param.getName()); System.out.println(); > >
В конструкторе класса ReflectionTest сначала вызываются процедуры определения полей и методов объекта/класса Reflect. После этого вызываются методы изменения значений и печати значений с использованием Reflection API. Для определения метода setData используется массив типов параметров. Вызов метода setData выполняется с передачей ему массива новых значений.
public class ReflectionTest < static Reflect reflect; public ReflectionTest() < getClassFields (reflect.getClass()); getClassMethods(reflect.getClass()); Classcls = reflect.getClass(); try < System.out.println("\n1. invoke method toString()\n"); Method method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); Class[] paramTypes; Object [] args; paramTypes = new Class[] ; method = cls.getMethod("setData", paramTypes); args = new Object[]; method.invoke(reflect, args); System.out.println("\n2. invoke method toString()\n"); method = cls.getMethod("toString"); System.out.println(method.invoke(reflect)); > catch (NoSuchMethodException e) < >catch (SecurityException e) < >catch (IllegalAccessException e) < >catch (IllegalArgumentException e) < >catch (InvocationTargetException e) < >> private void getClassFields(Class cls) < // код метода представлен выше >private void getClassMethods(Class cls) < // код метода представлен выше >public static void main(String[] args) < reflect = new Reflect(); new ReflectionTest(); System.exit(0); >>
В результате выполнения примера в консоль будут выведены представленные ниже сообщения. Методы setData и toString(), вызываемые с помощью Java Reflection API, вносят измнения в закрытые поля класса и распечатываются их значения.
Class fields Class name : name Class type : java.lang.String Class name : id Class type : int Class methods Method name : toString Return type : java.lang.String Parameters : Method name : getId Return type : int Parameters : Method name : setId Return type : void Parameters : int Method name : getName Return type : java.lang.String Parameters : Method name : setName Return type : void Parameters : java.lang.String Method name : setData Return type : void Parameters : int java.lang.String 1. invoke method toString() Reflect [ id : 999, name : Test] 2. invoke method toString() Reflect [ id : 123, name : New value]
Скачать пример
Исходный код рассмотренного примера вызова метода invoke с использованием Java Reflection API можно скачать здесь (989 байт).
Java Reflection API
Java Reflection API — это программный интерфейс в языке Java, который позволяет приложениям анализировать свои компоненты и программное окружение, изменять собственное поведение и структуру. Позволяет исследовать информацию о полях, методах и конструкторах классов.
Освойте профессию «Java-разработчик»
С помощью механизма рефлексии можно обрабатывать типы, которые отсутствовали при компиляции, но появились во время выполнения программы. Рефлексия и наличие логически целостной модели выдачи информации об ошибках позволяют создавать корректный динамический код.
Возможности
Помимо самомодификации, API способен проводить самопроверку и самоклонирование. Чаще всего рефлексию Java используют:
- для получения информации о классах, интерфейсах, функциях, конструкторах, методах и модулях;
- изменения имен функций и классов во время выполнения программы;
- создания новых экземпляров классов;
- анализа и исполнения кода, поступающего из программного окружения;
- преобразования классов из одного типа в другой;
- создания массивов данных и манипуляций с ними;
- установления значений полей объектов по именам;
- получения доступа к переменным и методам, включая приватные, и к внешним классам;
- вызова методов объектов по именам.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык
Особенности рефлексии в Java
Снижение производительности программы
Рефлексия работает медленнее, чем обычные приемы по обработке классов, методов и переменных. Это связано с тем, что во время динамического определения многих типов оптимизация производительности становится недоступной. Поэтому не следует применять Reflection API во фрагментах кода, которые часто используются приложением, в особенности если скорость выполнения программы — приоритет разработчика.
Блокировка диспетчером безопасности
Для запуска рефлексии в программировании требуется разрешение на выполнение, которое, как правило, не выдается при работе программного компонента под управлением Security Manager (диспетчера безопасности).
Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке
Уязвимость
При неверном использовании API способен нарушать один из главных принципов объектно-ориентированного программирования — инкапсуляцию данных. Это может привести к появлению потенциальных уязвимостей в веб-приложениях. В период с 2013 по 2016 год в библиотеке Reflection существовала брешь, которая позволяла хакерам обходить «песочницу» (изолированную зону для выполнения программ).
Нарушение переносимости программы
Поскольку Reflection API позволяет коду выполнять операции, которые обычно находятся под запретом, например получать доступ к закрытым полям и методам, использование рефлексии может сделать код неработоспособным и нарушить переносимость с одной операционной системы на другую. Кроме того, рефлексивный код нарушает абстракции, поэтому может изменить поведение программы при обновлении платформы.
Пример работы Reflection API в Java
Чтобы использовать Java Reflection API, не нужно подключать сторонние библиотеки. Все расположено в пакете java.lang.reflect.
Продемонстрируем некоторые методы рефлексии в программировании на конкретных примерах.
// Демонстрация работы Рефлексии в Java
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
// Создание объектов для класса Sample
// Создаем приватное поле private
// Создаем публичный конструктор
// Создаем публичный метод без параметров
public void method1() System.out.println(«Информация в строке — » + s); >
// Создаем публичный метод с целым числом в качестве параметра
public void method2(int x) System.out.println(«Целое число — » + x);
>
// Создаем приватный метод
private void method3() System.out.println(«Вызов приватного метода»);
>
>
class Exercise
public static void main(String args[]) throws Exception
// Создаем объект для последующей проверки свойств
Sample obj = new Sample();
// Создаем новый объект класса из другого объекта
Class cls = obj.getClass();
System.out.println(«Имя класса — » +
cls.getName());
// Получаем имя конструктора класса с помощью объекта
Constructor constructor = cls.getConstructor();
System.out.println(«Имя конструктора — » +
constructor.getName());
System.out.println(«Это публичные методы классов: «);
// Получаем методы классов с помощью объектов
Method[] methods = cls.getMethods();
// Выводим имена методов
for (Method method:methods)
System.out.println(method.getName());
// Создаем объект нужного метода с помощью имени метода и параметра класса
Method methodcall1 = cls.getDeclaredMethod(«method2», int.class);
// Вызов метода во время исполнения
// Создаем объект нужного поля с помощью имени поля
Field field = cls.getDeclaredField(«s»);
// Открываем доступ к полю независимо от используемого в нем спецификатора доступа
// Устанавливаем новое значение поля
// Создаем объект метода с помощью имени метода
Method methodcall2 = cls.getDeclaredMethod(«method1»);
// Вызов метода во время исполнения
// Создаем третий объект метода с помощью имени метода
Method methodcall3 = cls.getDeclaredMethod(«method3»);
// Изменяем настройки доступа
// Вызов метода во время исполнения
Рефлексия — мощный инструмент, для правильного использования которого требуются высокая квалификация и взвешенный подход.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.
Что такое Reflection и как его использовать?
Reflection, рефлексия – это средства манипуляции данными на основе знания о структуре классов этих данных, инструменты метапрограммирования.
Экземпляр класса Class можно получить тремя способами:
Литералом .class ;
Статическим фабричным методом Class.forName() ;
Методом getClass() экземпляров класса.
Использование Reflection API медленное и небезопасное. Оно позволяет ломать инвариантность состояний экземпляра, нарушать инкапсуляцию, и даже менять финальные поля.
Использовать рефлексию естественно в тестовом коде, в инструментах разработки, в фреймворках (особенно в связке с runtime-аннотациями). Рефлекшн в ординарном бизнес-коде обычно говорит о больших проблемах проектирования.
Нередко на интервью просят продемонстрировать пример использования рефлекшна. Один из самых близких для backend-разработчика примеров – инициализация классов-конфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности таких классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как объявления элементов экосистемы фреймворка.
Что такое рефлексия в java
Метод newInstance() позволяет создать объект указанного класса. Но что ВАЖНО отметить — такой вызов возможен только в случае, если класс имеет конструктор БЕЗ ПАРАМЕТРОВ. Если такого конструктора в вашем классе нет, то воспользоваться таким простым вызовом не получится — придется действовать сложнее. Так что иметь конструктор без параметров — это неплохая идея.
Как вы уже возможно догадались, вызов возвращает объект класса Object и нам надо вручную привести его к нужному типу. Т.к. мы полагаемся на “порядочность” нашего класса, которая выражается в поддержке интерфейса FinanceInfoBuilder, то мы к нему и приводим наш объект.
Теперь для смены класса для загрузки финансовых показателей достаточно просто отредактировать файл builder.properties. Ничего больше. Далее следуют бурные продолжительные аплодисменты.
Проект в NetBeans можно загрузить здесь: FinanceExample
Класс Class
Мы посмотрели вариант загрузки класса и создание объекта нужного нам класса. На самом деле такой вариант использования рефлексии весьма распространен. Так что очень рекомендую запомнить это решение. Теперь мы посмотрим, как можно не только создать объект нужного класса, но и как можно обратиться к полям или методам.
Определим простой класс для наших экспериментов
* Класс для демонстрации рефлексии
package edu . javacourse . reflection ;
public class SimpleClass
private String first ;
public String getFirst ( ) <
return first ;
public String simple ( ) <
return "Method1" ;
public String concat ( String s1 , String s2 ) <
return s1 + s2 ;
И теперь “поиграем” с нашим классом — обратимся к его полю и методам через Reflection API
* Класс для демонстрации рефлексии
package edu . javacourse . reflection ;
import java . lang . reflect . Field ;
import java . lang . reflect . Method ;
public class SimpleReflectionExample
public static void main ( String [ ] args ) <
//Пример обращения к полям и методам класса
demoReflection ( ) ;
> catch ( Exception ex ) <
ex . printStackTrace ( System . out ) ;
private static void demoReflection ( ) throws Exception <
// Заружаем описание класса
Class example = Class . forName ( "edu.javacourse.reflection.SimpleClass" ) ;
SimpleClass sc = ( SimpleClass ) example . newInstance ( ) ;
// Обращение к полю
demoReflectionField ( example , sc ) ;
// Обращение к методу
demoReflectionMethod ( example , sc ) ;
private static void demoReflectionField ( Class example , SimpleClass sc ) throws Exception <
// Получить обхект типа Field - обратите внимание, что поле private
Field f = example . getDeclaredField ( "first" ) ;
// Выставить разрешение для доступа к полю
f . setAccessible ( true ) ;
// Получить значение поля - оно у нас пока NULL
String test = ( String ) f . get ( sc ) ;
System . out . println ( "Field before SET:" + sc . getFirst ( ) ) ;
// Установить значение поля
f . set ( sc , "Test" ) ;
System . out . println ( "Field after SET:" + sc . getFirst ( ) ) ;
private static void demoReflectionMethod ( Class example , SimpleClass sc ) throws Exception <
// Вызов метода без параметров
// Получить обхект типа Method по имени
Method method1 = example . getMethod ( "simple" ) ;
// Вызвать метод с помощью invoke - передать туда только объект
String simple = ( String ) method1 . invoke ( sc ) ;
System . out . println ( "Simple:" + simple ) ;
// Вызов метода с параметрами
// Сначала надо определить список параметров - вспоминаем overloading
// У нас это две строки - String
Class [ ] paramTypes = new Class [ ] < String . class , String . class >;
// Получить обхект типа Method по имени и по списку параметров
Method concat = example . getMethod ( «concat» , paramTypes ) ;
// Вызвать метод — передать туда объект и два параметра типа строка
String answer = ( String ) concat . invoke ( sc , «1» , «2» ) ;
System . out . println ( «Concat:» + answer ) ;
Итак, давайте разбираться. Метод demoReflcetion уже знакомым нам способом создает объект типа SimpleClass. В принципе этот момент был не обязателен и выглядит достаточно натянуто — смысл загружать класс и следующей строкой его же и использовать, но для демонстрации подойдет. Дальше начинаются гораздо более интересные вещи.
Метод demoReflectionField показывает способ обращения к полю (причем к приватному полю). Самое главное — это получение обхекта типа Field по имени, с помощью которого можно уже работать с конкретным полем. Дальше код демонстрирует такие возможности.
Особо хочу отметить вызов setAccessible(true), который позволяет работать с приватным полем.
Метод demoReflectionMethod демонстрирует вариант получения метода по имени и по имени и параметрам — вспоминаем, что такое overloading. Здесь уже используется другой тип — Method — с помощью которого можно вызвать конкретный метод объекта. Дальше я предлагаю вам самим запустить пример а также прочитать код и комментарии к нему.
Проект в NetBeans можно загрузить здесь: SimpleReflection
Аннотации
В версии Java 1.5 появился очень интересный и очень мощный инструмент — аннотации. По сути аннотация — это именованный блок информации, который содержит набор именованных параметров и этот блок можно “прикрепить” к классу, методу, полю и даже параметру в методе. Другими словами — у аннотации есть имя и у нее есть список параметров с именами, которые можно выставить в определенные значения.
И теперь еще раз подумайте — вы можете прикрепить к основным артефактам кода (класс, метод, поле) блок с информацией и ЭТОТ БЛОК ДОСТУПЕН через рефлексию.
И что в этом таког, можете спросить вы ? Дело в том, что теперь есть возможность написать библиотеку (набор классов), которая может обрабатывать классы с определенными аннотациями. Например, именно так работает система ORM (Object Relation Mapping) — система сохранения объектов в базу данных (если честно, то не только так). С появлением аннотаций это теперь очень несложно сделать — вы аннотируете класс, который надо сохранить в базу данных специальным набором аннотаций и все. Дальше библиотека смотрит по аннотациям в какую таблицу и какое поле этого объекта к какую колонку записывается. Там еще можно “навесить” дополнительные условия, связи и много чего еще. Скорость разработки возрастает, написание системы упрощается, становится более лаконичной.
Точно также можно делать EJB, сервлеты. JUnit работает по этому принципу. Системы автоматического создания набора нужных объектов (IoC/DI — Inversion of Control/Dependency Injection) с нужными значениями полей использует аннотации. Веб-сервисы строятся на основе аннотаций, работа с XML и много чего еще.
По сути, библиотека просто говорит вам: “если у твоего класса есть такие-то аннотации, то я смогу произвести над ним нужную тебе работу. Просто напиши нужные аннотации с нужными параметрами”.
Аннотации настолько “вросли” в различные технологии Java, что на сегодня без них работать гораздо сложнее.
Для примера я создал свою аннотацию (хотя прикладной программист чаще всего использует уже готовые) для расширения нашего примера с финансовой информацией.
Вот как выглядит аннотация (это файл .java)