springboot~获取原注解的方法findMergedAnnotation使用场景
一 重要知识点
在Spring框架中,AnnotationUtils.findAnnotation()
和AnnotatedElementUtils.findMergedAnnotation()
是两种不同的注解查找方式,主要区别如下:
1. AnnotationUtils.findAnnotation()
- 特点:直接查找原始注解
- 局限性:
- 无法获取被元注解(如
@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);
}
}
}
}
四 总结
Spring通过AnnotatedElementUtils
和注解属性别名机制,能够正确识别你自定义注解中通过@AliasFor
映射的属性值。这种设计使得注解组合和自定义变得非常灵活,是Spring框架强大的元编程能力的体现。