springboot~获取原注解的方法findMergedAnnotation使用场景
- 特点:直接查找原始注解
- 局限性:
- 无法获取被元注解(如
@AliasFor)覆盖的属性值 - 无法处理注解属性覆盖(Annotation Attribute Overrides)场景
- 若注解是通过元注解(如
@Component派生出的@Service)间接存在,可能无法正确获取属性值
- 无法获取被元注解(如
2.AnnotatedElementUtils.findMergedAnnotation()
- 特点:查找"合并后的"注解
- 优势:
- 支持Spring的注解属性覆盖机制(通过
@AliasFor) - 会递归处理元注解,合并属性值
- 能正确获取经过覆盖后的最终属性值
- 支持查找接口/父类上的注解(通过
@Inherited)
- 支持Spring的注解属性覆盖机制(通过
示例场景差异
@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的处理过程:
- 发现注解:扫描到
@MyCustomJmsListener注解 - 识别元注解:发现
@MyCustomJmsListener被@JmsListener元注解标记 - 属性合并:通过
@AliasFor将value="my-queue"映射到destination属性 - 创建监听器:使用合成后的
@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); } } } }