如何使用@Retryable处理HttpServerErrorException子类的重试场景?
Spring Batch 502 BadGateway异常的两种重试实现方案
问题背景
使用Spring Batch时偶尔会碰到502 HTTP响应,对应的异常日志如下:
Error finding details for ids [123, 456 ] due to I/O error on POST request for "http://localhost:18080/my/api/path/findByIds": localhost:18080 failed to respond; nested exception is org.apache.http.NoHttpResponseException: localhost:18080 failed to respond ,level:err,thread:taskExecutor-1,logClass:c.t.k.e.r.myRepository,logMethod:findPersonByIds
服务端所有错误都会被包装在HttpServerErrorException中,BadGateway是该类的子类。现在需要实现两种重试逻辑:
- a) 仅当
BadGateway异常发生时执行重试; - b) 出现
BadGateway异常且其嵌套异常为NoHttpResponseException时执行重试。
之前直接使用@Retryable(value = NoHttpResponseException.class)未生效,原因是该异常是嵌套在BadGateway中的,Spring Retry默认只会匹配顶层异常。
场景a:仅针对BadGateway异常重试
直接在@Retryable的value参数中指定BadGateway.class即可,只要捕获到该异常就会触发重试:
import org.springframework.retry.annotation.Retryable; import org.springframework.web.client.HttpServerErrorException.BadGateway; @Retryable(value = BadGateway.class) public void myMethod() { // 此处编写调用外部API的业务逻辑 }
注意需在项目配置类上添加@EnableRetry注解,开启Spring Retry功能。
场景b:BadGateway异常且嵌套NoHttpResponseException时重试
这种情况需要判断顶层异常为BadGateway,同时其嵌套异常链中存在NoHttpResponseException,可通过两种方式实现:
方式一:SpEL表达式简化实现
直接在@Retryable中使用exceptionExpression属性,通过SpEL直接判断嵌套异常类型,无需额外编写类:
import org.springframework.retry.annotation.Retryable; import org.springframework.web.client.HttpServerErrorException.BadGateway; import org.apache.http.NoHttpResponseException; @Retryable( value = BadGateway.class, exceptionExpression = "#root.cause instanceof T(org.apache.http.NoHttpResponseException)" ) public void myMethod() { // 业务逻辑代码 }
方式二:自定义异常匹配器(适配复杂逻辑)
如果需要更灵活的重试规则(比如自定义重试次数、间隔),可以编写异常匹配器结合RetryTemplate使用:
- 编写异常匹配器类:
import org.springframework.web.client.HttpServerErrorException; import org.apache.http.NoHttpResponseException; public class BadGatewayWithNoHttpResponseMatcher implements org.springframework.retry.policy.ExceptionMatcher { @Override public boolean matches(Throwable throwable) { // 先判断顶层异常是否为BadGateway if (throwable instanceof HttpServerErrorException.BadGateway) { // 遍历嵌套异常链,检查是否存在NoHttpResponseException Throwable cause = throwable.getCause(); while (cause != null) { if (cause instanceof NoHttpResponseException) { return true; } cause = cause.getCause(); } } return false; } }
- 配置自定义
RetryTemplate:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.policy.ExceptionClassifierRetryPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; @Configuration @EnableRetry public class RetryConfig { @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // 基于异常分类策略,根据匹配结果决定重试规则 ExceptionClassifierRetryPolicy classifierPolicy = new ExceptionClassifierRetryPolicy(); classifierPolicy.setExceptionClassifier(throwable -> { BadGatewayWithNoHttpResponseMatcher matcher = new BadGatewayWithNoHttpResponseMatcher(); if (matcher.matches(throwable)) { // 匹配成功时,最多重试3次 SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(3); return retryPolicy; } else { // 不匹配则不重试 return new SimpleRetryPolicy(0); } }); retryTemplate.setRetryPolicy(classifierPolicy); return retryTemplate; } }
- 在业务方法中使用
RetryTemplate执行逻辑:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; @Service public class MyService { @Autowired private RetryTemplate retryTemplate; public void myMethod() { retryTemplate.execute(context -> { // 此处编写调用外部API的业务逻辑 return null; }); } }
内容的提问来源于stack exchange,提问作者Chaitanya Pandit
相关产品推荐
相关产品推荐

