Перейти к основному содержимому

Аннотации и рефлексия в Java

Разработчику Архитектору

Аннотации и рефлексия

Аннотация — метаданные, прикреплённые к объявлению (класс, метод, поле, параметр). Компилятор и фреймворки читают их для проверок, генерации кода или настройки поведения во время выполнения.

Рефлексия (java.lang.reflect) — инспекция и вызов классов, методов и полей во время выполнения, когда имена известны не на этапе компиляции.

Модули JPMS: ключевые слова, конфигурации и module-info. Spring и DI: Spring Framework.


Встроенные аннотации

АннотацияНазначение
@OverrideКомпилятор проверяет переопределение
@DeprecatedПометка устаревшего API; можно задать since, forRemoval
@SuppressWarningsПодавление предупреждений компилятора ("unchecked", "deprecation")
@FunctionalInterfaceОдин абстрактный метод — для лямбд
@SafeVarargsПодавление предупреждений о varargs и generics
@Override
public String toString() {
return "User{id=%d}".formatted(id);
}

Собственная аннотация

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timed {
String value() default "";
}
@RetentionКогда видна
SOURCEТолько исходник (например, для генераторов)
CLASSВ байт-коде, не в runtime
RUNTIMEДоступна через рефлексию
@TargetКуда можно ставить
TYPEКласс, интерфейс, enum
METHOD, FIELD, PARAMETERЧлены и параметры
CONSTRUCTORКонструктор

Использование:

public class ReportService {
@Timed("generate")
public byte[] generate() { ... }
}

Обработка на runtime (упрощённо):

for (Method m : ReportService.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(Timed.class)) {
Timed t = m.getAnnotation(Timed.class);
long start = System.nanoTime();
m.invoke(service);
log.info("{} took {} ms", t.value(), ...);
}
}

В реальных проектах ту же роль выполняют AOP (Spring @Timed, interceptors) и процессоры аннотаций при компиляции (MapStruct, Lombok — с осторожностью к зависимостям).


Рефлексия — основные типы

Class<?> clazz = Class.forName("com.example.User");
// или User.class

Constructor<?> ctor = clazz.getDeclaredConstructor(String.class);
Object user = ctor.newInstance("Ada");

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Grace");

Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true); // обход private — осторожно
idField.setLong(user, 1L);

getMethod — только public; getDeclaredMethod — все объявленные в классе.


Когда рефлексия уместна

  • Фреймворки: внедрение зависимостей, ORM, сериализация, JUnit.
  • Плагины и драйверы, загружаемые по имени класса.
  • Утилиты (IDE, миграции, анализ байт-кода).

Когда избегать в прикладном коде:

  • обычный бизнес-слой без веской причины;
  • hot path (медленнее прямого вызова, сложнее для JIT);
  • обход инкапсуляции setAccessible(true) без необходимости.

Рефлексия и модули (Java 9+)

Модуль может не открывать пакет для глубокого доступа. Типичная ошибка:

InaccessibleObjectException: module java.base does not "opens java.lang" ...

Решения в порядке предпочтения:

  1. Публичный API вместо взлома private.
  2. opens в module-info.java для своего кода.
  3. Флаги JVM --add-opens только для legacy-библиотек и тестов.

Подробнее: справочник конфигураций.


Аннотации в экосистеме

  • Jakarta Persistence / Bean Validation@Entity, @NotNull (рекомендации).
  • JUnit 5@Test, @ParameterizedTest (JUnit).
  • Spring@Component, @Autowired, @RestController (Spring).

Аннотации не выполняют логику сами: за ними стоит обработчик (контейнер, компилятор, агент).


Безопасность

Рефлексия и setAccessible обходят инкапсуляцию. В средах с SecurityManager (legacy) и при работе с недоверенным кодом это критично. В серверных приложениях не загружайте классы по произвольным строкам от пользователя.


Альтернативы «чистой» рефлексии

ЗадачаПодход
Маппинг DTOMapStruct, records, явные фабрики
Конфигурациятипобезопасные properties, records
Полиморфизминтерфейсы, sealed-классы (современный Java)

Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).