Статьи

Понимание API отражения Java за пять минут

Язык, способный к отражению, такой как 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).