Язык, способный к отражению, такой как Java, позволяет разработчикам проверять типы, методы, поля, аннотации и т. Д. Во время выполнения и откладывать решение о том, как их использовать, от времени компиляции до времени выполнения. Для этого API отражения Java предлагает такие типы, как Class
, Field
, Constructor
, Method
, Annotation
и другие. С ними можно взаимодействовать с типами, которые не были известны во время компиляции, например, для создания экземпляров неизвестного класса и вызова методов для них.
Этот быстрый совет предназначен для того, чтобы дать вам общее представление о том, что такое отражение, как оно выглядит в Java и для чего его можно использовать. После этого вы будете готовы начать или работать с более длинными учебниками. Чтобы получить максимальную отдачу от этого, вы должны хорошо понимать, как структурирована Java, в частности, что такое классы и методы и как они связаны. Знание об аннотациях открывает отдельный раздел.
Reflection API
Вместо того, чтобы строить с нуля, я хочу начать с простого примера. Сначала как простой Java-код:
URL url = new URL("https://sitepoint.com/java"); String urlString = url.toExternalForm(); System.out.println(urlString);
Я решил во время компиляции (имеется в виду, когда я писал код), что я хочу создать объект URL
, и вызвать некоторый метод в нем. Вот как я мог бы сделать то же самое с API отражения Java:
// the gateway to reflection is the `Class` instance // for the class you want to operate on Class<?> type = Class.forName("java.net.URL"); // fetches the constructor that takes a `String` argument // and uses it to create a new instance for the given string Constructor<?> constructor = type.getConstructor(String.class); Object instance = constructor.newInstance("https://sitepoint.com/java"); // fetches the `toExternalForm` method and invokes it on // the instance that was just created Method method = type.getMethod("toExternalForm"); Object methodCallResult = method.invoke(instance); System.out.println(methodCallResult);
Использование API отражения, конечно, более обременительно, чем непосредственное написание кода. Но таким образом, детали, которые раньше вставлялись в код (например, я использую URL
или метод, который я вызываю), становятся просто параметром. Как следствие, вместо того, чтобы toExternalForm
на URL
и toExternalForm
во время компиляции, они могут быть определены позже, когда программа уже запущена.
Большинство случаев использования для этого происходит в «рамочных» средах. Подумайте, например, о JUnit, который хочет выполнить все методы, аннотированные @Test
. Найдя их с помощью сканирования пути класса, он использует getMethod
и invoke
для вызова. Spring и другие веб-фреймворки действуют аналогично при поиске контроллеров и отображений запросов. Расширяемые приложения, которые хотят загружать предоставленные пользователем плагины во время выполнения, являются еще одним примером использования.
Основные типы и методы
Class::forName
в API отражения является Class::forName
. В своей простой форме этот статический метод просто берет полное имя класса и возвращает для него экземпляр Class
. Этот экземпляр можно использовать для получения полей , методов , конструкторов и многого другого.
Чтобы получить конкретный конструктор, метод getConstructor
может быть вызван с типами аргументов конструктора, как я делал выше. Точно так же к определенным методам можно получить доступ, вызвав getMethod
и передав его имя, а также типы параметров. getMethod("toExternalForm")
выше не указал никаких типов, потому что метод не имеет аргументов.
Вот метод, который делает:
Class<?> type = Class.forName("java.net.URL"); // `URL::openConnection` has an overload that accepts a java.net.Proxy Method openConnection = type.getMethod("openConnection", Proxy.class);
Экземпляры, возвращаемые этими вызовами, имеют тип Constructor
и Method
, соответственно. Для вызова базового члена они предлагают методы, такие как Constructor::newInstance
и Method::invoke
. Интересной деталью последнего является то, что экземпляр, для которого вызывается метод, должен быть передан ему в качестве первого аргумента. Другие аргументы будут переданы вызываемому методу.
Продолжая пример openConnection
:
openConnection.invoke(instance, someProxy);
Если должен быть вызван статический метод, аргумент экземпляра будет проигнорирован и, следовательно, может быть null
.
Аннотации
Аннотации являются важной частью размышления. На самом деле, аннотации в основном направлены на рефлексию. Они предназначены для предоставления метаинформации, доступ к которой можно получить во время выполнения, а затем использовать для формирования поведения программы. (Как уже упоминалось, JUnit’s @Test
и Spring @Controller
и @RequestMapping
являются отличными примерами.)
Все важные типы, связанные с отражением, такие как Class
, Field
, Constructor
, Method
и Parameter
реализуют интерфейс AnnotatedElement
. Связанный Javadoc содержит подробное объяснение того, как аннотации могут относиться к этим элементам (непосредственно присутствующим, косвенно представленным или связанным), но его простейшая форма заключается в следующем: метод getAnnotations
возвращает аннотации, присутствующие в этом элементе, в виде массива экземпляров Annotation
, чьи члены могут быть доступны.
Резюме
API рефлексии Java позволяет выполнять самоанализ типов, методов, аннотаций и т. Д. Во время выполнения, а также вызывать конструкторы и методы, которые не были известны во время компиляции. Для начала вызовите Class.forName("fully.qualified.class.Name")
а затем getConstructors
, getMethods
, getAnnotations
или аналогичные методы. Вызов происходит с newInstance
для конструкторов и invoke
для методов.
Вы также можете использовать рефлексию для взлома кода и изменения непубличных полей или вызова непубличных методов — это рискованная практика, и в Java 9 она становится еще сложнее . Если вам любопытно и вы хотели бы знать все входы и выходы API, попробуйте найти отражение в официальной документации Java . К настоящему времени API немного устарел и имеет некоторые недостатки, но существуют новые альтернативы, ознакомьтесь с дескрипторами методов (начиная с Java 7) и с переменными (начиная с Java 9).