Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
@Retention: три уровня хранения аннотаций
Основная концепция
@Retention контролирует, на каком этапе жизненного цикла программы аннотация остаётся доступной. Есть три значения (RetentionPolicy):
1. RetentionPolicy.SOURCE
Аннотация существует только в исходном коде, удаляется компилятором.
@Retention(RetentionPolicy.SOURCE)
public @interface Deprecated {
// Используется только в .java файлах
// При компиляции удаляется
// В .class файле НЕ будет
// В runtime НЕ доступна
}
// Пример использования
@SuppressWarnings("unchecked") // SOURCE - удалится при компиляции
public class OldClass {
// Компилятор видит аннотацию
// но в .class файле её не будет
}
Когда использовать:
- Аннотации для компилятора (@Override, @SuppressWarnings)
- Аннотации для инструментов анализа кода
- Подсказки для IDE
- Не нужны в runtime
Примеры:
// @Override - помощь компилятору при переопределении
@Override
public void run() {
// Если метод не переопределяет родительский - ошибка компиляции
}
// @SuppressWarnings - отключение предупреждений компилятора
@SuppressWarnings("unchecked")
public List getList() {
return new ArrayList(); // без аннотации - warning
}
// Своя аннотация для компилятора
@Retention(RetentionPolicy.SOURCE)
public @interface NotNull {
// IDE подсветит если передать null
}
Преимущества:
- Минимальный размер .class файла
- Нет overhead на runtime
- Компилятор может проверить корректность
Недостатки:
- Недоступна в runtime
- Нельзя использовать для reflection
2. RetentionPolicy.CLASS
Аннотация хранится в .class файле, но недоступна в runtime через reflection.
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
// Будет в .class файле
// НЕ будет в runtime при работе программы
// Нельзя получить через reflection
// Доступна инструментам обработки bytecode
}
// Пример
@MyAnnotation(value = "test")
public class MyClass {
// Компилятор добавит информацию в .class
// Но если запросить при runtime - null
}
MyAnnotation annotation = MyClass.class.getAnnotation(MyAnnotation.class);
// annotation будет null!
Когда использовать:
- Аннотации для инструментов bytecode instrumentation
- Аннотации для APM (Application Performance Monitoring)
- Аннотации для code coverage (Jacoco)
- Не нужны при выполнении программы
Примеры:
// Для инструмента code coverage
@Retention(RetentionPolicy.CLASS)
public @interface InstrumentForCoverage {
// Jacoco добавит информацию для подсчёта покрытия
}
// Для APM инструмента
@Retention(RetentionPolicy.CLASS)
public @interface Trace {
// Инструмент обработает bytecode и добавит логирование
}
// Для статического анализатора
@Retention(RetentionPolicy.CLASS)
public @interface AnalyzeMe {
// FindBugs / SpotBugs будут анализировать
}
Это значение по умолчанию если @Retention не указана:
public @interface DefaultRetention {
// Если нет @Retention - используется CLASS
// Будет в .class но не в runtime
}
Преимущества:
- Доступна инструментам
- Не требует runtime processing
- Минимальный overhead
Недостатки:
- Недоступна для reflection в runtime
3. RetentionPolicy.RUNTIME
Аннотация сохраняется в runtime и доступна через reflection.
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRuntimeAnnotation {
String value() default "";
int priority() default 0;
// Будет в .class файле
// Будет доступна в runtime через reflection
}
// Использование
@MyRuntimeAnnotation(value = "important", priority = 1)
public class MyClass {
// Аннотация будет доступна при выполнении
}
// Получение в runtime
MyRuntimeAnnotation annotation = MyClass.class
.getAnnotation(MyRuntimeAnnotation.class);
if (annotation != null) {
System.out.println(annotation.value()); // "important"
System.out.println(annotation.priority()); // 1
}
Когда использовать:
- Dependency Injection (Spring @Autowired, @Component)
- Сериализация (JSON @JsonProperty)
- ORM (JPA @Entity, @Column)
- Test frameworks (JUnit @Test, @Before)
- Кастомные фреймворки требующие reflection
Примеры из популярных фреймворков:
// Spring Framework
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
// Spring при startup ищет через reflection
// и внедряет зависимости
}
// JPA
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
// Hibernate ищет через reflection
// и создаёт映射
}
// JUnit
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
// Фреймворк ищет методы с этой аннотацией
// и запускает их как тесты
}
// Свой фреймворк
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredRole {
String value();
}
public class SecurityFilter {
public void filter(Method method) {
RequiredRole annotation = method
.getAnnotation(RequiredRole.class);
if (annotation != null) {
String requiredRole = annotation.value();
// Проверить, есть ли у пользователя роль
if (!userHasRole(requiredRole)) {
throw new AccessDeniedException();
}
}
}
}
Практический пример: простой Dependency Injection контейнер
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}
public class DIContainer {
public <T> T getInstance(Class<T> clazz) {
T instance = clazz.newInstance();
// Ищем поля с @Inject
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
// Инъектируем зависимость
Object dependency = getInstance(field.getType());
field.setAccessible(true);
field.set(instance, dependency);
}
}
return instance;
}
}
// Использование
public class UserService {
@Inject
private UserRepository userRepository; // автоматически внедрится
}
UserService service = new DIContainer().getInstance(UserService.class);
Преимущества:
- Доступна при выполнении программы
- Можно использовать reflection для обработки
- Гибко: фреймворк может принять решение в runtime
Недостатки:
- Больший размер .class файла
- Overhead на runtime (reflection)
- Нужно обрабатывать вручную
Сравнение трёх видов
| Policy | В исходном коде | В .class файле | В runtime | Reflection | Когда использовать |
|---|---|---|---|---|---|
| SOURCE | ✓ | ✗ | ✗ | ✗ | Подсказки компилятору, IDE |
| CLASS | ✓ | ✓ | ✗ | ✗ | Инструменты bytecode, coverage |
| RUNTIME | ✓ | ✓ | ✓ | ✓ | Фреймворки, DI, ORM |
Как проверить уровень retention
public class AnnotationUtils {
public static void checkRetention(Class<?> annotationClass) {
Retention retention = annotationClass
.getAnnotation(Retention.class);
if (retention == null) {
System.out.println("Default: CLASS");
} else {
RetentionPolicy policy = retention.value();
System.out.println("Policy: " + policy);
}
}
}
AnnotationUtils.checkRetention(Override.class); // SOURCE
AnnotationUtils.checkRetention(Entity.class); // RUNTIME
Практическое правило
// Использовать SOURCE если:
// - Нужна только для компилятора
// - Не нужна в runtime
@Override // Ошибка компиляции если неправильно
public void method() {}
// Использовать CLASS если:
// - Нужна инструментам bytecode
// - НЕ нужна в runtime
// Это значение по умолчанию
@interface BytecodeInstruction {}
// Использовать RUNTIME если:
// - Нужна в runtime
// - Нужна reflection
// - Используется в фреймворке
@Retention(RetentionPolicy.RUNTIME)
@interface Framework {}
Вывод
Три вида @Retention:
- SOURCE - только в исходном коде, удаляется компилятором
- CLASS - в .class файле, но не в runtime (по умолчанию)
- RUNTIME - сохраняется и доступна через reflection при выполнении
Выбирай в зависимости от того, когда нужна информация об аннотации: когда компилируется, когда обрабатывается bytecode, или когда запущена программа.