Bazaprogram.ru

Новости из мира ПК
2 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java reflection method invoke

Рефлексия кода, reflection

Рефлексия (от reflexio — обращение назад) — это механизм исследования данных о программе во время её выполнения. Рефлексия в Java осуществляется с помощью Java Reflection API, состоящий из классов пакетов java.lang и java.lang.reflect. В информатике рефлексия означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.

Java Reflection API позволяет получать информацию о конструкторах, методах и полях классов и выполнять следующие операции над полями и методами объекта/класса :

  • определение класса объекта;
  • получение информации о полях, методах, конструкторах и суперклассах;
  • получение информации о модификаторах полей и методов;
  • создание экземпляра класса, имя которого неизвестно до момента выполнения программы;
  • определение и изменение значений свойств объекта/класса;
  • вызов методов объекта/класса.

Примечание : в тексте используется объект/класс. При работе с объектом (реализацией класса) можно обращаться к полям и методам класса напрямую, если они доступны (не private). При работе с классом можно обращаться к методам класса с использованием Java Reflection API. Но класс необходимо получить из объекта.

Определение свойств класса

В работающем приложении для получения класса необходимо использовать метод forName (String className). Следующий код демонстрирует возможность создания класса без использования и с использованием Reflection :

Метод класса forName(className) часто используется для загрузки JDBC-драйвера.

Методом getName() объекта Class можно получить наименование класса, включающего пакет (package) :

Для получения значения модификатора класса используется метод getModifiers(). Класс java.lang.reflect.Modifier содержит статические методы, возвращающие логическое значения проверки модификатора класса :

Для получения суперкласса рефлексированного объекта (класса) необходимо использовать метод getSuperclass() :

Поскольку в Java отсутствует множественное наследование, то для получения всех предков следует рекурсивно вызвать метод getSuperclass() в цикле, пока не будет достигнут Object, являющийся родителем всех классов. Object не имеет родителей, поэтому вызов его метода getSuperclass() вернет null.

Определение интерфейсов и конструкторов класса

Для получения в режиме run-time списка реализующих классом интерфейсов, необходимо получить Class и использовать его метод getInterfaces(). В следующем примере извлекается список интерфейсов класса ArrayList :

Чтобы IDE (Eclipse) не предупреждала о необходимости определения типа класса

Class is a raw type. References to generic type Class should be parameterized

в коде были использованы generic’и. В консоль выводятся следующие интерфейсы, реализуемые классом ArrayList :

Метод класса getConstructors() позволяет получить массив открытых конструкторов типа java.lang.reflect.Constructor. После этого, можно извлекать информацию о типах параметров конструктора и генерируемых исключениях :

Определение полей класса

Метод getFields() объекта Class возвращает массив открытых полей типа java.lang.reflect.Field, которые могут быть определены не только в данном классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Field позволяет получить имя поля, тип и модификаторы :

Если известно наименование поля, то можно получить о нем информацию с помощью метода getField() объекта Class.

Методы getField() и getFields() возвращают только открытые члены данных класса. Чтобы получить все поля класса, включая закрытые и защищенные, необходимо использовать методы getDeclaredField() и getDeclaredFields(). Данные методы работают точно так же, как и их аналоги getField() и getFields().

Определение значений полей класса

Класс Field содержит специализированные методы для получения значений примитивных типов: getInt(), getFloat(), getByte() и др. Для установки значения поля, используется метод set(). Для примитивных типов имеются методы setInt(), setFloat(), setByte() и др.

Ниже приведен пример изменения значения закрытого поля класса в режиме run-time.

Определение методов класса

Метод getMethods() объекта Class возвращает массив открытых методов типа java.lang.reflect.Method. Эти методы могут быть определены не только в классе, но также и в его родителях (суперклассе), либо интерфейсах, реализованных классом или его родителями. Класс Method позволяет получить имя метода, тип возвращаемого им значения, типы параметров метода, модификаторы и генерируемые исключения.

Если известно имя метода и типы его параметров, то можно получить отдельный метод класса :

Пример изменения значения закрытого поля класса

Чтобы изменить значение закрытого (private) поля класса необходимо получить это поле методом getDeclaredField () и вызвать метод setAccessible (true) объекта Field, чтобы открыть доступ к полю. После этого значение закрытого поля можно изменять, если оно не final. В следующем примере определен внутренний класс PrivateFinalFields с набором закрытых полей; одно из полей final. При создании объекта класса поля инициализируются. В методе main примера поочередно в закрытые поля вносятся изменения и свойства объекта выводятся в консоль.

В результате выполнения примера в консоль будут выведены следующие сообщения :

Из приведённого примера видно что поля 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 для печати их значений.

Читать еще:  Java lang nosuchmethodexception

Для тестирования объекта типа Reflect с помощью Java Reflection Api создадим класс ReflectionTest. В этот класс включим две процедуры getClassFields и getClassMethods, которые в режиме run-time распечатают всю информацию (описание полей и методов) о классе. Методы получают класс в качестве параметра. В процедурах сначала определяются массивы полей и методы; после этого их параметры распечатываются :

В конструкторе класса ReflectionTest сначала вызываются процедуры определения полей и методов объекта/класса Reflect. После этого вызываются методы изменения значений и печати значений с использованием Reflection API. Для определения метода setData используется массив типов параметров. Вызов метода setData выполняется с передачей ему массива новых значений.

В результате выполнения примера в консоль будут выведены представленные ниже сообщения. Методы setData и toString(), вызываемые с помощью Java Reflection API, вносят измнения в закрытые поля класса и распечатываются их значения.

Скачать пример

Исходный код рассмотренного примера вызова метода invoke с использованием Java Reflection API можно скачать здесь (989 байт).

Вызов методов через reflection

Все программисты на Java явно или неявно пользуются reflection для вызова методов. Даже если вы не делали этого сами, это за вас наверняка делают библиотеки или фреймворки, которые вы используете. Давайте посмотрим, как этот вызов устроен внутри и насколько это быстро. Будем глядеть в OpenJDK 8 с последними обновлениями.

Начать изучение стоит собственно с метода Method.invoke. Там делается три вещи:

  • Проверяются права доступа на метод.
  • Создаётся и запоминается MethodAccessor , если его ещё нет (если данный метод ещё не вызывали через reflection).
  • Вызывается MethodAccessor.invoke .

Проверка прав доступа состоит из двух частей. Быстрая проверка устанавливает, что и метод, и содержащий его класс имеют модификаторы public . Если это не так, то проверяется, что у вызывающего класса есть доступ к данному методу. Чтобы узнать вызывающий класс, используется приватный метод Reflection.getCallerClass() . Некоторые люди, кстати, любят им пользоваться в своём коде. В Java 9 появится публичный Stack-Walking API и в высшей степени разумно будет перейти на него.

Известно, что проверки доступа можно отменить, вызвав заранее method.setAccessible(true) . Этот сеттер устанавливает флаг override , позволяющий игнорировать проверки. Даже если вы знаете, что ваш метод публичный, установка setAccessible(true) позволит сэкономить немного времени на проверках.

Давайте посмотрим, сколько разные сценарии съедают времени. Напишем простой класс с публичным и непубличным методами:

Напишем JMH-тест, параметризованный двумя флагами: accessible и nonpublic . Такой у нас будет подготовка:

Я вижу вот такие результаты (3 форка, 5x500ms прогрев, 10x500ms измерение):

(accessible)(nonpublic)Time
truetrue5,062 ± 0,056 ns/op
truefalse5,042 ± 0,032 ns/op
falsetrue6,078 ± 0,039 ns/op
falsefalse5,835 ± 0,028 ns/op

Действительно если выполнен setAccessible(true) , то получается быстрее всего. При этом без разницы, публичный метод или нет. Если же setAccessible(false) , то оба теста медленнее и непубличный метод чуть медленнее публичного. Впрочем я ожидал, что различие будет сильнее. Главным образом тут помогает, что Reflection.getCallerClass() — это интринсик JIT-компилятора, который в большинстве случаев подменяется просто константой во время компиляции: если JIT-компилятор инлайнит вызов Method.invoke, он знает, в какой метод он его инлайнит, а значит и знает, что должен вернуть getCallerClass() . Дальше проверка по сути сводится к сравнению пакета вызываемого и вызывающего класса. Если бы пакет был разный, проверялась бы ещё иерархия классов.

Что же происходит дальше? Дальше нужно создать объект MethodAccessor . Кстати, несмотря на то что Person.class.getMethod(«getName») всегда вернёт новый экземпляр объекта Method , используемый внутри MethodAccessor будет переиспользован через поле root, что, конечно, приятно. Тем не менее сам getMethod существенно медленнее вызова, поэтому если вы планируете вызывать метод несколько раз, разумно хранить объект Method .

Созданием MethodAccessor ‘а занимается ReflectionFactory. Здесь мы видим два сценария, которые контролируются глобальными настройками JVM:

  • Если установлена опция -Dsun.reflect.noInflation=true (по умолчанию выключена), то сразу генерируется вспомогательный класс, который и будет запускать целевой метод.
  • Иначе создаётся обёртка DelegatingMethodAccessorImpl , внутрь которой помещается NativeMethodAccessorImpl . В свою очередь он считает, сколько раз данный метод вызывали. Если количество вызовов превысило порог, заданный через -Dsun.reflect.inflationThreshold (по умолчанию 15), то происходит «раздувание» аксессора: генерируется вспомогательный класс, как и в первом сценарии. Если же порог не достигнут, вызов идёт честно через JNI. Хотя реализация на стороне C++ тривиальна, накладные расходы на JNI весьма высоки.

Давайте посмотрим, что будет с нашим тестом, если включить -Dsun.reflect.noInflation=true и если использовать только JNI (для этого зададим большой порог -Dsun.reflect.inflationThreshold=100000000 ):

(accessible)(nonpublic)DefaultnoInflationJNI-only
truetrue5,062 ± 0,0564,935 ± 0,375195,960 ± 1,873
truefalse5,042 ± 0,0324,914 ± 0,329194,722 ± 1,151
falsetrue6,078 ± 0,0395,638 ± 0,050196,196 ± 0,910
falsefalse5,835 ± 0,0285,520 ± 0,042194,626 ± 0,918

Здесь и далее все результаты в наносекундах на операцию. Как и ожидалось, JNI существенно медленнее, поэтому включать такой режим неоправданно. Любопытно, что режим noInflation оказался чуть быстрее. Это происходит из-за того, что отсутствует DelegatingMethodAccessorImpl , который убирает необходимость в одной косвенной адресации. По умолчанию вызов проходит через Method → DelegatingMethodAccessorImpl → GeneratedMethodAccessorXYZ , а с этой опцией цепочка сокращается до Method → GeneratedMethodAccessorXYZ . Вызов Method → DelegatingMethodAccessorImpl мономорфный и легко девиртуализируется, но косвенная адресация всё равно остаётся.

Кстати, о девиртуализации. Стоит заметить, что бенчмарк у нас плохой, потому что он не отображает ситуацию в реальной программе. В бенчмарке мы вызываем через reflection только один метод, а значит, у нас всего один сгенерированный аксессор, который тоже легко девиртуализуется и даже инлайнится. В реальном приложении так не бывает: аксессоров много. Чтобы сэмулировать эту ситуацию, давайте опционально отравим профиль типов в методе setup :

Обратите внимание, что мы никак не меняли код, производительность которого мы измеряем. Мы просто сделали несколько тысяч с виду бесполезных вызовов перед этим. Однако эти бесполезные вызовы немного подпортят картинку: JIT видит, что вариантов много, и не может подставить единственный возможный, делая теперь честный виртуальный вызов. Результаты будут такие (poly — вариант с превращением вызова метода в полиморфный, на JNI не влияет):

(acc)(nonpub)DefaultDefault/polynoInflationnoInflation/polyJNI-only
truetrue5,062 ± 0,0566,848 ± 0,0314,935 ± 0,3756,509 ± 0,032195,960 ± 1,873
truefalse5,042 ± 0,0326,847 ± 0,0354,914 ± 0,3296,490 ± 0,037194,722 ± 1,151
falsetrue6,078 ± 0,0397,855 ± 0,0405,638 ± 0,0507,661 ± 0,049196,196 ± 0,910
falsefalse5,835 ± 0,0287,568 ± 0,0465,520 ± 0,0427,111 ± 0,058194,626 ± 0,918

Как видно, виртуальный вызов добавляет около 1,5-1,8 нс на моём железе — даже больше, чем проверки доступа. Важно помнить, что поведение виртуальной машины в микробенчмарке может существенно отличаться от поведения в реальном приложении, и по возможности воссоздавать условия, близкие к реальности. Здесь, конечно, от реальности всё ещё далеко: как минимум, все нужные объекты в L1-кэше процессора и сборка мусора не происходит, потому что мусора нет.

Некоторые могут подумать, круто, мол, что с -Dsun.reflect.noInflation=true всё становится быстрее. Пусть всего на 0,3 нс, но всё же. Да плюс первые 15 вызовов ускорятся. Да и рабочий набор чуть уменьшился, кэш процессора экономим — сплошные плюсы! Добавим опцию в продакшн и заживём! Так делать не надо. В бенчмарке мы протестировали один сценарий, а в природе бывают и другие. Например, какой-нибудь код может по одному разу вызывать множество разных методов. С этой опцией аксессор будет генерироваться сразу на первом вызове. А сколько это стоит? Сколько времени генерируется аксессор?

Чтобы это оценить, можно через reflection очищать приватное поле Method.methodAccessor (очистив предварительно Method.root ), принудив инициализировать аксессор заново. Запись поля через reflection хорошо оптимизирована, поэтому от этого тест сильно не замедлится. Получаем такие результаты. Верхняя строка — ранее полученные результаты (polymorph, accessible), для сравнения:

(test)DefaultnoInflationJNI
invoke6,848 ± 0,0316,509 ± 0,032195,960 ± 1,873
reset+invoke227,133 ± 9,159100195,746 ± 2060,810236,900 ± 2,042

Как видим, если аксессор сбрасывать, то по дефолту производительность становится немного хуже, чем в варианте с JNI. А вот если мы от JNI полностью отказываемся, то получаем 100 микросекунд на запуск метода. Генерация и загрузка класса в рантайме по сравнению с однократным вызовом метода (даже через JNI), конечно, чудовищно медленна. Поэтому дефолтное поведение «пробовать 15 раз через JNI и только потом генерировать класс» кажется в высшей степени разумным.

Вообще помните, что нет волшебной опции, которая ускорит любое приложение. Если бы она была, она бы была включена по умолчанию. Какой смысл её прятать от людей? Возможно, есть опция, которая ускорит конкретно ваше приложение, но не принимайте на веру любые советы типа «врубите -XX:+MakeJavaFaster, и всё будет летать».

Как же выглядят эти сгенерированные аксессоры? Байткод генерируется в классе MethodAccessorGenerator с использованием довольно тривиального низкоуровневого API ClassFileAssembler, которое чем-то похоже на урезанную библиотеку ASM. Классам даются имена вида sun.reflect.GeneratedMethodAccessorXYZ , где XYZ — глобальный синхронизированный счётчик, вы их можете увидеть в стектрейсах и отладчике.

Сгенерированный класс существует только в памяти, но мы легко можем сдампить его на диск, добавив в метод ClassDefiner.defineClass строчку вида

После этого можно смотреть на класс в декомпиляторе. Для нашего метода getName1() сгенерировался такой код (декомпилятор FernFlower и ручное переименование переменных):

Заметьте, сколько приходится делать дополнительных вещей. Надо проверить, что нам передали непустой объект нужного типа и передали пустой список аргументов или null вместо списка аргументов (не все знают, но при вызове через reflection метода без аргументов мы можем передать null вместо пустого массива). При этом надо аккуратно соблюдать контракт: если вместо объекта передали null , то выкинуть NullPointerException . Если передали объект другого класса, то IllegalArgumentException . Если произошло исключение при выполнении person.getName1() , то тогда InvocationTargetException . И это ещё у метода нет аргументов. А если они есть? Вызовем, например, такой метод (для разнообразия теперь статический и возвращающий void ):

Теперь кода существенно больше:

Заметьте, что вместо int мы имеем право передать Byte , Short , Character или Integer , и всё это обязано преобразоваться. Именно здесь преобразование и идёт. Такой блок будет добавляться для каждого примитивного аргумента, где возможно расширяющее преобразование. Теперь также понятно, зачем в catch ловится NullPointerException : он может возникнуть при анбоксинге и тогда мы обязаны также выдать IllegalArgumentException . Зато благодаря тому, что метод статический, нас совершенно не волнует, что в параметре target . Ну и появилась строчка return null , потому что наш метод возвращает void . Вся эта магия аккуратно расписана в MethodAccessorGenerator.emitInvoke.

Вот так примерно и работает вызов методов. Похожим образом устроен вызов конструкторов. Также этот код частично переиспользован для десериализации объектов. Когда аксессор уже существует, с точки зрения JVM он не особо отличается от кода, который вы бы написали сами вручную, поэтому рефлекшн начинает работать весьма быстро.

Reflection API в Java. Класс Method. Часть 3

  • написана командой Vertex Academy. Надеемся, что она Вам будет полезна. Приятного прочтения!
  • это одна из статей из нашего «Самоучителя по Java»
  • Данная статья предполагает, что Вы уже хорошо знаете ООП.

Эта статья является ответвлением от статьи Reflection API в Java, Часть 1, где мы узнали как получить Method из класса Class.

В этой статье вы узнаете как работать с классом Method.

Класс Method

Класс Method предоставляет возможность:

  • получить название метода, его модификаторы, тип возвращаемого значения и входящих параметров
  • получить аннотации метода, бросаемые исключения и другую информацию
  • вызвать метод, даже приватный

Для начала, создадим класс Car

Пример 1 Получение названия метода

Пример 2 Получение модификаторов метода

С методами история та же, что и с полями. Для работы с модификаторами метода необходимо воспользоваться методами класса Modifiers

Пример 3 Получение типа возвращаемого значения

Метод getGenericReturnType возвращает параметризованный тип. Лучше всего это видно на следующем примере

Реальный тип возвращаемого значения будет Object т.к. во время исполнения все дженерики являются Object. А вот genericReturnType будет T, как раз тот тип, что мы указали в методе logAndReturn().

Пример 4 Получение аннотаций метода

Для получения аннотаций метода существуют методы : getAnnotations() и getDeclaredAnnotations(), getAnnotationsByType() и getDeclaredAnnotationsByType(), getAnnotation() и getDeclaredAnnotation()

Но, пары методов getAnnotations() и getDeclaredAnnotations(), getAnnotationsByType() и getDeclaredAnnotationsByType(), getAnnotation() и getDeclaredAnnotation() делают одно и то же. Как пример, javadoc для getDeclaredAnnotation():

Поэтому мы рассмотрим только несколько из них.

  • getAnnotations() возвращает массив аннотаций метода
  • getAnnotation() возвращает аннотацию по типу
  • getAnnotationsByType() возвращает массив аннотаций по типу. Метод был добавлен в Java 8 вместе с @Repeatable аннотациями

Пример 5 Получение входящих параметров

  • getParameterCount() возвращает количество входящих параметров
  • getParameters() возвращает массив всех входящих параметров в виде класса Parameter
  • getParameterTypes() возвращает массив типов входящих параметров в виде класса Class
  • getGenericParameterTypes() возвращает массив дженерик входящих типов параметров.
  • getTypeParameters() возвращает массив дженериков входящих типов в виде класса TypeVariable
  • getParameterAnnotations() возвращает массив аннотаций входящих параметров

Пример 6 Получение бросаемых исключений

Call Methods at Runtime Using Java Reflection

Last modified: September 8, 2019

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

In the 9 years of running Baeldung, I’ve never, ever done a «sale».
But. we’ve also not been through anything like this pandemic either.
And, if making my courses more affordable for a while is going to help a company stay in business, or a developer land a new job, make rent or be able to provide for their family — then it’s well worth doing.
Effective immediately, all Baeldung courses are 33% off their normal prices!
You’ll find all three courses in the menu, above, or here.

1. Overview

In this short article, we’ll take a quick look at how to invoke methods at runtime using the Java Reflection API.

2. Getting Ready

Let’s create a simple class which we’ll use for the examples that follow:

3. Obtaining a Method Object

First, we need to get a Method object that reflects the method we want to invoke. The Class object, representing the type in which the method is defined, provides two ways of doing this.

3.1. getMethod()

We can use getMethod() to find any public method, be it static or instance that is defined in the class or any of its superclasses.

It receives the method name as the first argument, followed by the types of the method’s arguments:

3.2. getDeclaredMethod()

We can use getDeclaredMethod() to get any method defined in the class. This includes public, protected, default access, and even private methods but excludes inherited ones.

It receives the same parameters as getMethod():

4. Invoking Methods

With the Method instance in place, we can now call invoke() to execute the underlying method and get the returned object.

4.1. Instance Methods

To invoke an instance method, the first argument to invoke() must be an instance of Method that reflects the method being invoked:

4.2. Static Methods

Since these methods don’t require an instance to be called, we can pass null as the first argument:

5. Method Accessibility

By default, not all reflected methods are accessible. This means that the JVM enforces access control checks when invoking them.

For instance, if we try to call a private method outside its defining class or a protected method from outside a subclass or its class’ package, we’ll get an IllegalAccessException:

By calling setAccesible(true) on a reflected method object, the JVM suppresses the access control checks and allows us to invoke the method without throwing an exception:

6. Conclusion

In this quick article, we’ve seen how to call instance and static methods of a class at runtime through reflection. We also showed how to change the accessible flag on the reflected method objects to suppress Java access control checks when invoking private and protected methods.

As always, the example code can be found over on Github.

Ссылка на основную публикацию
Adblock
detector