← Назад к вопросам

Что такое RetentionPolicy для аннотаций в Java?

2.0 Middle🔥 191 комментариев
#SOLID и паттерны проектирования#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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");
        }
    }
}

Лучшие практики

  1. Используй RUNTIME для фреймворков и фич

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ApiEndpoint { }
    
  2. Используй SOURCE для помощи компилятору

    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    public @interface SuppressWarnings { }
    
  3. Думай о производительности

    • RUNTIME аннотации занимают дополнительную память
    • Если не нужна runtime информация, используй SOURCE или CLASS
  4. Документируй назначение

    /**
     * Кеширует результат метода на 5 минут.
     * Использует RetentionPolicy.RUNTIME для работы с Reflection
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Cacheable { }
    

Заключение

RetentionPolicy — это критичный инструмент для управления жизненным циклом аннотаций. Выбор правильной политики влияет на:

  • Производительность (меньше памяти с SOURCE)
  • Функциональность (нужна RUNTIME для фреймворков)
  • Безопасность (скрытие информации с SOURCE)

Понимание RetentionPolicy необходимо для создания собственных аннотаций и работы с фреймворками, которые полагаются на runtime аннотации.