springboot~獲取原注解的方法findMergedAnnotation使用場景(jing)
一 重要知識點
在Spring框架中,AnnotationUtils.findAnnotation()和AnnotatedElementUtils.findMergedAnnotation()是兩(liang)種不(bu)同的(de)注解查找方(fang)式(shi),主要區別如下:
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,除(chu)非明確需要訪問(wen)未(wei)經處理(li)的原始注解。
二 Spring的注解屬性別名的應用
當你在自定義注解中使用@AliasFor為@JmsListener的destination屬性賦值(zhi)時,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處理注(zhu)解屬性的核心(xin)方法:
// 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. 實際示例
假設你的(de)使用方式如(ru)下:
@Component
public class MyMessageListener {
@MyCustomJmsListener("my-queue")
public void handleMessage(String message) {
// 處理消息
}
}
三(san) Spring JMS的(de)處(chu)理過程:
- 發現注解:掃描到
@MyCustomJmsListener注解 - 識別元注解:發現
@MyCustomJmsListener被@JmsListener元注解標記 - 屬性合并:通過
@AliasFor將value="my-queue"映射到destination屬性 - 創建監聽器:使用合成后的
@JmsListener(destination = "my-queue")創建JMS監聽容器
驗證方法
你可以通(tong)過(guo)以下方式驗(yan)證(zheng)這個(ge)機(ji)制:
@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映射的屬(shu)性值。這種設計(ji)使得(de)注解組合和(he)自(zi)定(ding)義變得(de)非常靈(ling)活,是Spring框(kuang)架(jia)強大(da)的元編程能力的體現。