Что такое RetentionPolicy для аннотаций в Java?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
RetentionPolicy: жизненный цикл аннотаций
RetentionPolicy — это enum в Java (пакет java.lang.annotation), который определяет, как долго аннотация будет сохраняться после компиляции кода. Это управляет жизненным циклом аннотации на трёх уровнях: исходный код, скомпилированный байт-код и runtime.
RetentionPolicy — это мета-аннотация, которая применяется к самому определению аннотации через @Retention.
Три уровня RetentionPolicy
┌──────────────────────────────────────────────────────┐
│ ИСХОДНЫЙ КОД (.java файлы) │
│ @Retention(RetentionPolicy.SOURCE) │
└──────────────────────────────────────────────────────┘
↓ Компиляция
┌──────────────────────────────────────────────────────┐
│ БАЙТКОД (.class файлы) │
│ @Retention(RetentionPolicy.CLASS) │
└──────────────────────────────────────────────────────┘
↓ Runtime выполнение
┌──────────────────────────────────────────────────────┐
│ RUNTIME ПАМЯТЬ (JVM) │
│ @Retention(RetentionPolicy.RUNTIME) │
└──────────────────────────────────────────────────────┘
RetentionPolicy.SOURCE
Аннотация существует только в исходном коде и удаляется при компиляции.
Используется для:
- Аннотаций, которые нужны только компилятору
- Подавления предупреждений компилятора
- Информации для обработчиков аннотаций (annotation processors)
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface DebugInfo {
String author();
String date();
String description();
}
public class Example {
@DebugInfo(
author = "John",
date = "2025-03-23",
description = "Temporary debug method"
)
public void debugMethod() {
System.out.println("Debugging");
}
}
// В скомпилированном .class файле:
// - Аннотация @DebugInfo отсутствует
// - При runtime через Reflection не сможешь получить эту аннотацию
Примеры стандартных аннотаций с RetentionPolicy.SOURCE:
// @Override - помощь компилятору
@Retention(RetentionPolicy.SOURCE)
public @interface Override { }
// @Deprecated - информирует компилятор
@Retention(RetentionPolicy.RUNTIME) // Но часто используется с SOURCE
public @interface Deprecated { }
// @FunctionalInterface - проверка функциональности интерфейса
@Retention(RetentionPolicy.SOURCE)
public @interface FunctionalInterface { }
RetentionPolicy.CLASS
Аннотация сохраняется в байт-коде, но НЕ доступна при runtime через Reflection.
Это значение по умолчанию, если @Retention не указана.
Используется для:
- Инструментирования байт-кода (bytecode enhancement)
- Обработки кода во время сборки
- Анализа статического байт-кода
import java.lang.annotation.*;
// Это значение по умолчанию
@Retention(RetentionPolicy.CLASS) // или просто не указывать
@Target(ElementType.METHOD)
public @interface Performance {
long maxTimeMs() default 1000;
}
public class Service {
@Performance(maxTimeMs = 500)
public void fastMethod() {
// Операция
}
}
// Во время runtime:
Method method = Service.class.getMethod("fastMethod");
Performance annotation = method.getAnnotation(Performance.class);
if (annotation != null) {
System.out.println("Max time: " + annotation.maxTimeMs());
} else {
System.out.println("Аннотация не доступна в runtime!");
// Вывод: Аннотация не доступна в runtime!
}
RetentionPolicy.RUNTIME
Аннотация сохраняется в байт-коде И доступна при runtime через Reflection API.
Используется для:
- Фреймворков (Spring, Hibernate, Jackson)
- Dependency injection
- Обработки аннотаций во время выполнения
- Генерации документации
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {
String key();
int ttl() default 3600;
}
public class UserService {
@Cacheable(key = "user:", ttl = 7200)
public User getUser(Long id) {
return database.findById(id);
}
}
// Во время runtime можем прочитать аннотацию:
public class CacheInterceptor {
public void processMethod(Method method) {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable != null) {
String cacheKey = cacheable.key() + method.getName();
int ttl = cacheable.ttl();
System.out.println("Кешируем с ключом: " + cacheKey + ", TTL: " + ttl);
}
}
}
Практические примеры
Пример 1: Валидация с RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
String message() default "Field cannot be null";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Email {
String message() default "Invalid email format";
}
public class User {
@NotNull(message = "Name is required")
private String name;
@Email
private String email;
@NotNull
private String password;
}
// Валидатор, работающий с runtime аннотациями
public class Validator {
public static <T> void validate(T object) throws ValidationException {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
// Проверяем NotNull аннотацию
NotNull notNull = field.getAnnotation(NotNull.class);
if (notNull != null) {
field.setAccessible(true);
try {
Object value = field.get(object);
if (value == null) {
throw new ValidationException(notNull.message());
}
} catch (IllegalAccessException e) {
throw new ValidationException(e.getMessage());
}
}
// Проверяем Email аннотацию
Email email = field.getAnnotation(Email.class);
if (email != null && field.getType().equals(String.class)) {
field.setAccessible(true);
try {
String value = (String) field.get(object);
if (!isValidEmail(value)) {
throw new ValidationException(email.message());
}
} catch (IllegalAccessException e) {
throw new ValidationException(e.getMessage());
}
}
}
}
private static boolean isValidEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}
// Использование
public class Main {
public static void main(String[] args) throws ValidationException {
User user = new User();
user.name = "John";
user.email = "invalid-email"; // Ошибка!
Validator.validate(user); // Выбросит ValidationException
}
}
Пример 2: Кеширование с RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {
String value();
int ttl() default 3600;
}
public class CacheManager {
private Map<String, Object> cache = new HashMap<>();
private Map<String, Long> timestamps = new HashMap<>();
public Object executeWithCache(Object target, Method method, Object[] args) throws Exception {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable == null) {
// Нет аннотации, просто выполняем
return method.invoke(target, args);
}
String cacheKey = cacheable.value() + ":" + Arrays.hashCode(args);
// Проверяем кеш
if (cache.containsKey(cacheKey)) {
long cacheTime = timestamps.getOrDefault(cacheKey, 0L);
long elapsed = System.currentTimeMillis() - cacheTime;
if (elapsed < cacheable.ttl() * 1000) {
System.out.println("Возвращаем из кеша: " + cacheKey);
return cache.get(cacheKey);
} else {
// TTL истёк, удаляем
cache.remove(cacheKey);
timestamps.remove(cacheKey);
}
}
// Выполняем метод
System.out.println("Выполняем метод: " + method.getName());
Object result = method.invoke(target, args);
// Сохраняем в кеш
cache.put(cacheKey, result);
timestamps.put(cacheKey, System.currentTimeMillis());
return result;
}
}
public class UserService {
@Cacheable("user", ttl = 300) // 5 минут
public User getUser(Long id) {
System.out.println("Запрос к БД для пользователя: " + id);
return new User(id, "John Doe", "john@example.com");
}
}
Пример 3: Spring аннотации с RetentionPolicy.RUNTIME
// Spring использует runtime аннотации для своей магии
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
String value() default "";
}
@Service("userService")
public class UserServiceImpl implements UserService {
// Spring найдёт эту аннотацию при runtime и зарегистрирует bean
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transactional {
String propagation() default "REQUIRED";
String isolation() default "DEFAULT";
}
public class OrderService {
@Transactional(propagation = "REQUIRES_NEW")
public void placeOrder(Order order) {
// Spring создаст транзакцию для этого метода
}
}
Сравнение RetentionPolicy
| Policy | Исходный код | Байт-код | Runtime | Использование |
|---|---|---|---|---|
| SOURCE | ✓ | ✗ | ✗ | Помощь компилятору |
| CLASS | ✓ | ✓ | ✗ | Инструментирование байт-кода |
| RUNTIME | ✓ | ✓ | ✓ | Фреймворки, reflection |
Отражение (Reflection) и RetentionPolicy
public class AnnotationReflection {
public static void inspectClass(Class<?> clazz) {
System.out.println("Класс: " + clazz.getName());
// Получить все аннотации (только RUNTIME)
Annotation[] annotations = clazz.getAnnotations();
System.out.println("Аннотаций: " + annotations.length);
for (Annotation annotation : annotations) {
System.out.println(" - " + annotation);
}
// Получить аннотацию определённого типа
Deprecated deprecated = clazz.getAnnotation(Deprecated.class);
if (deprecated != null) {
System.out.println("Класс помечен как deprecated");
}
}
}
Лучшие практики
-
Используй RUNTIME для фреймворков и фич
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiEndpoint { } -
Используй SOURCE для помощи компилятору
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface SuppressWarnings { } -
Думай о производительности
- RUNTIME аннотации занимают дополнительную память
- Если не нужна runtime информация, используй SOURCE или CLASS
-
Документируй назначение
/** * Кеширует результат метода на 5 минут. * Использует RetentionPolicy.RUNTIME для работы с Reflection */ @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Cacheable { }
Заключение
RetentionPolicy — это критичный инструмент для управления жизненным циклом аннотаций. Выбор правильной политики влияет на:
- Производительность (меньше памяти с SOURCE)
- Функциональность (нужна RUNTIME для фреймворков)
- Безопасность (скрытие информации с SOURCE)
Понимание RetentionPolicy необходимо для создания собственных аннотаций и работы с фреймворками, которые полагаются на runtime аннотации.