You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring @Retryable作为元注解使用时如何支持指定监听器

问题描述

项目中多处使用配置高度一致的@Retryable注解,为了减少重复配置,自定义了组合@Retryable的元注解@RESTClientRetry,代码如下:

@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
@Retryable(
        include = RestClientException.class,
        maxAttempts = 5,
        backoff = @Backoff(delay = 1000, multiplier = 3)
    )
public @interface RESTClientRetry {

    @AliasFor(annotation = Retryable.class, attribute = "listeners")
    String[] listeners() default {};

    @AliasFor(annotation = Retryable.class, attribute = "include")
    Class<? extends Throwable>[] include() default {};

    @AliasFor(annotation = Retryable.class, attribute = "maxAttemptsExpression")
    String maxAttemptsExpression() default "";

    @AliasFor(annotation = Retryable.class, attribute = "backoff")
    Backoff backoff() default @Backoff();
}

该注解的基础重试功能运行正常,但业务上需要给不同的注解使用场景指定专属监听器,使用方式如下:

@RESTClientRetry(listeners = "retryLoggerBean1")

当前应用中存在多个RetryListener类型的Bean,名称分别为retryLoggerBean1、retryLoggerBean2等,但实际运行时所有RetryListener Bean都会被所有标注了@RESTClientRetry的方法触发,即使每个注解已经明确指定了自身的listeners属性。
调试发现,AnnotationAwareRetryOperationsInterceptor执行时,解析得到的Retryable对象listeners属性为空,说明该拦截器没有处理组合注解场景下的属性覆写逻辑。由于无法获取到注解上指定的监听器,框架会回退加载全局所有监听器,这就是所有方法都会触发全部监听器的根本原因。
核心疑问:

  • 这个问题是AnnotationAwareRetryOperationsInterceptor的已知功能限制还是Bug?
  • 是否有其他方式封装@Retryable为自定义注解,同时支持不同场景单独指定生效的监听器?
解答

这是Spring Retry 1.3.0版本之前的已知功能限制,不属于Bug。
1.3.0之前的版本中,AnnotationAwareRetryOperationsInterceptor只会直接读取方法或类上声明的原生@Retryable注解,不会通过Spring的注解工具类解析组合注解上通过@AliasFor声明的属性覆写,因此自定义注解上配置的listeners等参数不会被同步到元注解@Retryable的解析结果中。

要实现自定义组合注解+单独指定监听器的需求,有两种可行方案:

方案1:升级Spring Retry版本至1.3.0及以上

从1.3.0版本开始,Spring Retry官方已经对组合注解场景做了适配,拦截器内部改用AnnotatedElementUtils.findMergedAnnotation()方法获取合并了所有覆写属性的@Retryable注解实例,你当前编写的@RESTClientRetry不需要做任何修改,指定listeners的逻辑就可以正常生效。

方案2:无法升级版本时,自定义拦截器替换默认实现

如果受项目依赖限制不能升级版本,可以自行扩展拦截器实现属性合并:

  • 继承AnnotationAwareRetryOperationsInterceptor,重写注解解析逻辑,用AnnotatedElementUtils.findMergedAnnotation()替换原生的直接读取注解的逻辑,获取合并了自定义注解属性的@Retryable实例
  • 将自定义拦截器注册为Spring Bean,替换框架默认的重试拦截器
    注意:自定义注解中声明的backoff属性默认值不要写空@Backoff(),否则属性合并时会覆盖元注解上配置的默认退避策略(delay=1000、multiplier=3)。如果要保留元注解的默认退避配置,可以不在自定义注解中声明backoff的别名,或者给backoff设置和元注解完全一致的默认值。

内容的提问来源于stack exchange,提问作者E-Riz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 04:33:26