springboot~获取原注解的方法findMergedAnnotation使用场景

一 重要知识点

在Spring框架中,AnnotationUtils.findAnnotation()AnnotatedElementUtils.findMergedAnnotation()是两种不同的注解查找方式,主要区别如下:

1. AnnotationUtils.findAnnotation()

  • 特点:直接查找原始注解
  • 局限性
    • 无法获取被元注解(如@AliasFor)覆盖的属性值
    • 无法处理注解属性覆盖(Annotation Attribute Overrides)场景
    • 若注解是通过元注解(如@Component派生出的@Service)间接存在,可能无法正确获取属性值

2. AnnotatedElementUtils.findMergedAnnotation()

  • 特点:查找"合并后的"注解
  • 优势
    • 支持Spring的注解属性覆盖机制(通过@AliasFor
    • 会递归处理元注解,合并属性值
    • 能正确获取经过覆盖后的最终属性值
    • 支持查找接口/父类上的注解(通过@Inherited

示例场景差异

@Spec(name = "defaultName") // 元注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Spec {
    String name() default "";
}

@Spec(name = "customName") // 实际使用的注解
public void someMethod() {}

// 测试结果:
AnnotationUtils.findAnnotation() -> 可能返回原始元注解的name="defaultName"
AnnotatedElementUtils.findMergedAnnotation() -> 会返回合并后的name="customName"

何时使用?

  • 需要原始注解时 → AnnotationUtils
  • 需要实际生效的注解属性时 → AnnotatedElementUtils
  • Spring注解处理(如@Transactional等组合注解) → 优先使用AnnotatedElementUtils

建议在Spring环境下优先使用AnnotatedElementUtils,除非明确需要访问未经处理的原始注解。

二 Spring的注解属性别名的应用

当你在自定义注解中使用@AliasFor@JmsListener的destination属性赋值时,Spring通过以下步骤处理:

1. 注解处理流程

// 你的自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@JmsListener
public @interface MyCustomJmsListener {
    
    @AliasFor(annotation = JmsListener.class, attribute = "destination")
    String value() default "";
    
    // 其他属性...
}

2. Spring JMS的内部处理机制

JmsListenerAnnotationBeanPostProcessor是处理@JmsListener的核心类:

// 简化的处理逻辑
public class JmsListenerAnnotationBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 扫描bean的方法,查找JmsListener注解
        for (Method method : bean.getClass().getMethods()) {
            // 这里会使用Spring的AnnotationUtils找到注解
            JmsListener jmsListener = AnnotatedElementUtils.findMergedAnnotation(
                method, JmsListener.class);
            
            if (jmsListener != null) {
                processJmsListener(jmsListener, method, bean);
            }
        }
        return bean;
    }
    
    private void processJmsListener(JmsListener jmsListener, Method method, Object bean) {
        // 获取destination值
        String destination = jmsListener.destination();
        // 创建监听器容器...
    }
}

3. 关键方法:AnnotatedElementUtils.findMergedAnnotation()

这是Spring处理注解属性的核心方法:

// Spring内部的处理逻辑
public static <A extends Annotation> A findMergedAnnotation(
        AnnotatedElement element, Class<A> annotationType) {
    
    // 1. 查找直接或元注解
    Annotation[] annotations = element.getAnnotations();
    
    for (Annotation ann : annotations) {
        // 2. 如果是目标注解直接返回
        if (annotationType.isInstance(ann)) {
            return (A) ann;
        }
        
        // 3. 递归处理元注解
        Annotation[] metaAnnotations = ann.annotationType().getAnnotations();
        for (Annotation metaAnn : metaAnnotations) {
            if (annotationType.isInstance(metaAnn)) {
                // 4. 处理属性别名映射
                return synthesizeAnnotation(ann, metaAnn, element);
            }
        }
    }
    return null;
}

4. 属性别名解析过程

private static <A extends Annotation> A synthesizeAnnotation(
        Annotation sourceAnnotation, 
        Annotation metaAnnotation, 
        AnnotatedElement element) {
    
    Map<String, Object> attributeMap = new HashMap<>();
    
    // 获取元注解的属性
    Method[] metaMethods = metaAnnotation.annotationType().getDeclaredMethods();
    for (Method metaMethod : metaMethods) {
        String attributeName = metaMethod.getName();
        
        // 检查源注解是否有对应的别名属性
        Method sourceMethod = findAliasMethod(sourceAnnotation, attributeName);
        if (sourceMethod != null) {
            // 使用源注解的值覆盖元注解的值
            Object value = invokeMethod(sourceMethod, sourceAnnotation);
            attributeMap.put(attributeName, value);
        } else {
            // 使用元注解的默认值
            Object value = invokeMethod(metaMethod, metaAnnotation);
            attributeMap.put(attributeName, value);
        }
    }
    
    // 创建合成注解
    return AnnotationUtils.synthesizeAnnotation(attributeMap, 
        metaAnnotation.annotationType(), element);
}

5. 实际示例

假设你的使用方式如下:

@Component
public class MyMessageListener {
    
    @MyCustomJmsListener("my-queue")
    public void handleMessage(String message) {
        // 处理消息
    }
}

三 Spring JMS的处理过程:

  1. 发现注解:扫描到@MyCustomJmsListener注解
  2. 识别元注解:发现@MyCustomJmsListener@JmsListener元注解标记
  3. 属性合并:通过@AliasForvalue="my-queue"映射到destination属性
  4. 创建监听器:使用合成后的@JmsListener(destination = "my-queue")创建JMS监听容器

验证方法

你可以通过以下方式验证这个机制:

@SpringBootTest
class JmsListenerTest {
    
    @Autowired
    private JmsListenerEndpointRegistry endpointRegistry;
    
    @Test
    void testCustomAnnotation() {
        // 检查监听器容器是否创建成功
        Collection<MessageListenerContainer> containers = 
            endpointRegistry.getListenerContainers();
        
        for (MessageListenerContainer container : containers) {
            if (container instanceof JmsListenerEndpointRegistry) {
                // 验证destination是否正确设置
                String destination = ((AbstractJmsListenerContainer) container)
                    .getDestination();
                System.out.println("监听的destination: " + destination);
            }
        }
    }
}

四 总结

Spring通过AnnotatedElementUtils和注解属性别名机制,能够正确识别你自定义注解中通过@AliasFor映射的属性值。这种设计使得注解组合和自定义变得非常灵活,是Spring框架强大的元编程能力的体现。

posted @ 2025-09-23 17:40  张占岭  阅读(51)  评论(0)    收藏  举报