Можно ли в Spring реализовать выполнение кастомного кода при навешивании аннотации без использования аспектов
Что проверяют
Понимаете ли вы, как Spring сам обрабатывает свои аннотации, и знаете ли точки расширения контейнера кроме @Aspect. Следом обычно спрашивают, как устроены @Async или @Transactional и почему аннотация не срабатывает при вызове метода изнутри того же класса.
Как отвечать
Да, и сам Spring так делает: @Scheduled, @Async, @EventListener обрабатываются не аспектами, а инфраструктурными бинами. Основные варианты:
BeanPostProcessor: вpostProcessAfterInitializationнаходите аннотацию на классе бина и либо выполняете код (регистрация, подписка, проверка настроек), либо возвращаете вместо бина обёртку. Так работаютScheduledAnnotationBeanPostProcessorиAsyncAnnotationBeanPostProcessor;- прокси вручную:
ProxyFactoryсMethodInterceptor, который проверяет аннотацию на вызываемом методе и выполняет код до и после. Это инфраструктура Spring AOP, но без аспектов и синтаксиса AspectJ; SmartInitializingSingletonили слушательContextRefreshedEvent: после старта контекста пройтись поgetBeansWithAnnotationи зарегистрировать найденное;HandlerInterceptorдля контроллеров: вpreHandleпроверяетеhandler instanceof HandlerMethod hmиhm.hasMethodAnnotation(RateLimited.class);ImportBeanDefinitionRegistrar: для аннотаций в духе@EnableSomething, добавляющих бины.
Для генерации кода на этапе компиляции есть обработчики аннотаций (APT), как у Lombok и MapStruct, но это уже не Spring.
У аннотации обязательно @Retention(RetentionPolicy.RUNTIME), иначе рефлексия её не увидит.
Где спотыкаются
Проверяют bean.getClass().isAnnotationPresent(...), а бин уже обёрнут CGLIB-прокси, и аннотации на методах не находятся. Нужны AopUtils.getTargetClass(bean) и AnnotatedElementUtils, которые учитывают прокси и мета-аннотации.
Забывают про самовызов: this.method() идёт мимо прокси, и обёртка не срабатывает, как и у @Transactional.
Объявляют BeanPostProcessor обычным @Bean в конфигурации с зависимостями. Он создаётся слишком рано, тянет за собой другие бины, и в логе появляется «is not eligible for getting processed by all BeanPostProcessors». Такой метод делают static.