@Recover 方法不会被@Retryable 触发

@Recover methods are not triggered with @Retryable

@Retryable 函数上获得 ExhaustedRetryException 后,我遵循了这个 @Retryable 函数正在重试。

这是具有 @Retryable 功能的委托:

@Component
public class OrderRequestDelegate {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderRequestDelegate.class);

    private final OrderRequestDao orderRequestDao;
    private final SqsQueueDao sqsQueueDao;

    @Autowired
    public OrderRequestDelegate(OrderRequestDao orderRequestDao, SqsQueueDao sqsQueueDao) {
        this.orderRequestDao = orderRequestDao;
        this.sqsQueueDao = sqsQueueDao;
    }

    @Retryable(include=NoResultException.class, backoff = @Backoff(delay = 100, maxDelay = 101), maxAttempts = 5)
    public OrderRequest processItem(String storeId, String message) {
        Long id = 999L;
        OrderRequest result = orderRequestDao.findOne(id);
        if (result == null) {
            LOGGER.info("Tried in retryable");
            throw new NoResultException();
        }
        return result;
    }

    @Recover
    public void recover(NoResultException e, Long id) {
        LOGGER.info("recover triggered");
    }

    @Recover
    public void recover(Exception e, Long id) throws Exception {
        LOGGER.info("retry failure");
    }
}

这是函数调用者的 class:

@RestController
@RequestMapping("/orderRequests")
@Api(description = "orders API")
public class OrderRequestController {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderRequestController.class);

    private final OrderRequestDelegate orderRequestDelegate;

    @Autowired
    public OrderRequestController(OrderRequestDelegate orderRequestDelegate) {
        this.orderRequestDelegate = orderRequestDelegate;
    }

    //...  

    @ApiOperation(value="test function")
    @RequestMapping(method = RequestMethod.GET)
    public String get() {
        orderRequestDelegate.processItem("100", "abc");
        return String.format("Worked");
    }
}

以及包含 @EnableRetry 注释的应用程序 class:

@SpringBootApplication
@EnableRetry
public class FulfillmentApplication extends SpringBootServletInitializer {

    private static final Logger LOGGER = LoggerFactory.getLogger(FulfillmentApplication.class);

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(FulfillmentApplication.class);
    }

    public static void main(String[] args) {
        LOGGER.debug("Starting Spring application main...");
        SpringApplication.run(FulfillmentApplication.class, args);
    }
}

这是我 运行 @Retryable 函数时的堆栈跟踪:

2017-01-27 12:58:21.918  INFO 93294 --- [nio-8080-exec-1] c.c.f.delegate.OrderRequestDelegate      : Tried in retryable
2017-01-27 12:58:22.022  INFO 93294 --- [nio-8080-exec-1] c.c.f.delegate.OrderRequestDelegate      : Tried in retryable
2017-01-27 12:58:22.129  INFO 93294 --- [nio-8080-exec-1] c.c.f.delegate.OrderRequestDelegate      : Tried in retryable
2017-01-27 12:58:22.231  INFO 93294 --- [nio-8080-exec-1] c.c.f.delegate.OrderRequestDelegate      : Tried in retryable
2017-01-27 12:58:22.336  INFO 93294 --- [nio-8080-exec-1] c.c.f.delegate.OrderRequestDelegate      : Tried in retryable
2017-01-27 12:58:22.352 ERROR 93294 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is javax.persistence.NoResultException] with root cause

javax.persistence.NoResultException: null
    at com.cfa.fulfillmentApi.delegate.OrderRequestDelegate.processItem(OrderRequestDelegate.java:44) ~[classes/:na]
    at com.cfa.fulfillmentApi.delegate.OrderRequestDelegate$$FastClassBySpringCGLIB$$f297a63a.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.retry.interceptor.RetryOperationsInterceptor.doWithRetry(RetryOperationsInterceptor.java:74) ~[spring-retry-1.1.2.RELEASE.jar:na]
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:263) ~[spring-retry-1.1.2.RELEASE.jar:na]
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:168) ~[spring-retry-1.1.2.RELEASE.jar:na]
    at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:98) ~[spring-retry-1.1.2.RELEASE.jar:na]
    at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:118) ~[spring-retry-1.1.2.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at com.cfa.fulfillmentApi.delegate.OrderRequestDelegate$$EnhancerBySpringCGLIB$ec8aa9.processItem(<generated>) ~[classes/:na]
    at com.cfa.fulfillmentApi.controller.OrderRequestController.get(OrderRequestController.java:45) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_112]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_112]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_112]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:220) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) ~[spring-boot-1.4.3.RELEASE.jar:1.4.3.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:105) ~[spring-boot-actuator-1.4.3.RELEASE.jar:1.4.3.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.4.3.RELEASE.jar:1.4.3.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_112]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_112]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.6.jar:8.5.6]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_112]

这是该答案中的完整应用程序,已修改为与您的应用程序功能相同;如果您仍然无法找出问题所在,请使用完整配置和调试日志编辑您的问题。

@SpringBootApplication
@EnableRetry
public class So38601998Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(So38601998Application.class, args);
        Foo bean = context.getBean(Foo.class);
        try {
            bean.out("foo");
            System.out.println("Recovered");
        }
        catch (Exception e) {
            System.out.println("Not recovered: " + e);
        }
        try {
            bean.out("bar");
        }
        catch (Exception e) {
            System.out.println("Not recovered: " + e);
        }
    }


    @Component
    public static class Foo {

        @Retryable(include=NoResultException.class, backoff = @Backoff(delay = 100, maxDelay = 101), maxAttempts = 5)
        public void out(String foo) {
            System.out.println(foo);
            if (foo.equals("foo")) {
                throw new NoResultException();
            }
            else {
                throw new IllegalStateException();
            }
        }

        @Recover
        public void connectionException(NoResultException e) {
            System.out.println("Retry failure");
        }

        @Recover
        public void connectionException(Exception e) throws Exception {
            System.out.println("Retry failure");
            throw e;
        }

    }

}

结果(使用 o.s.retry 的 DEBUG 日志记录)

14:16:25.803 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=0
foo
14:16:25.913 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=1
14:16:25.914 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=1
foo
14:16:26.017 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=2
14:16:26.017 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=2
foo
14:16:26.121 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=3
14:16:26.121 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=3
foo
14:16:26.223 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=4
14:16:26.223 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=4
foo
14:16:26.224 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=5
14:16:26.224 [main] DEBUG o.s.retry.support.RetryTemplate - Retry failed last attempt: count=5
Retry failure
Recovered
14:16:26.224 [main] DEBUG o.s.retry.support.RetryTemplate - Retry: count=0
bar
14:16:26.225 [main] DEBUG o.s.retry.support.RetryTemplate - Checking for rethrow: count=1
14:16:26.225 [main] DEBUG o.s.retry.support.RetryTemplate - Retry failed last attempt: count=1
Retry failure
Not recovered: java.lang.IllegalStateException

看来你和我一样只是错过了基础部分。

用@Recover注解的方法应该采用相同类型的参数(以及可选的Throwable或其子类类型参数)。除此之外 return 类型也应该相同。

下面是您的恢复方法在重试耗尽后应该如何被触发。

@Component
public static class Foo {

    @Retryable(include=NoResultException.class, backoff = `@Backoff(delay = 100, maxDelay = 101), maxAttempts = 5)`
    public void out(String foo) {
        System.out.println(foo);
        if (foo.equals("foo")) {
            throw new NoResultException();
        }
        else {
            throw new IllegalStateException();
        }
    }

    @Recover
    public void connectionException(NoResultException e, String foo) {
        System.out.println("Retry failure");
    }

    @Recover
    public void connectionException(Exception e, String foo) throws Exception {
        System.out.println("Retry failure");
        throw e;
    }

}