如何在@Retryable中排除读取超时但保留连接超时重试?
区分读取超时与连接超时的重试控制
问题核心在于:SocketTimeoutException(读取超时)是被ResourceAccessException包裹的嵌套异常,而@Retryable的exclude属性仅匹配顶层异常类型,不会递归检查嵌套异常,因此直接排除SocketTimeoutException无法生效。以下是三种可行的解决方法:
方法一:自定义RetryPolicy(最灵活)
通过实现自定义重试策略,在判断是否重试时递归检查异常链,精准区分读取超时和连接超时:
import org.springframework.retry.RetryContext; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.web.client.ResourceAccessException; import java.net.ConnectTimeoutException; import java.net.SocketTimeoutException; public class CustomRestRetryPolicy extends SimpleRetryPolicy { @Override public boolean canRetry(RetryContext context) { Throwable lastException = context.getLastThrowable(); // 若为读取超时,直接返回不重试 if (isReadTimeout(lastException)) { return false; } // 其他情况沿用父类的重试规则 return super.canRetry(context); } // 递归检查异常链,判断是否为读取超时 private boolean isReadTimeout(Throwable throwable) { if (throwable == null) { return false; } if (throwable instanceof ResourceAccessException) { Throwable cause = throwable.getCause(); return cause instanceof SocketTimeoutException; } return isReadTimeout(throwable.getCause()); } }
接着在配置类中注册这个自定义策略:
@Configuration @EnableRetry public class RetryConfig { @Bean public CustomRestRetryPolicy customRestRetryPolicy() { return new CustomRestRetryPolicy(); } }
最后在业务方法上指定使用该策略:
@Retryable(policy = "customRestRetryPolicy", maxAttempts = 2, exclude = {HttpClientErrorException.BadRequest.class}) public ResponseEntity<String> testMethod() throws Exception { return restTemplateRtp.exchange("http://localhost:8080/api/test", HttpMethod.POST, request, String.class); }
方法二:捕获异常并重新抛出区分类型的自定义异常
在业务方法内部捕获ResourceAccessException,解析嵌套异常后抛出不同的自定义异常,通过@Retryable的exclude属性精准控制:
首先定义两个自定义异常:
// 读取超时异常,用于标记不重试的场景 public class ReadTimeoutException extends RuntimeException { public ReadTimeoutException(Throwable cause) { super(cause); } } // 连接超时异常,用于标记需要重试的场景 public class ConnectTimeoutException extends RuntimeException { public ConnectTimeoutException(Throwable cause) { super(cause); } }
修改业务方法逻辑:
@Retryable(value = {RestClientException.class, ConnectTimeoutException.class}, exclude = {HttpClientErrorException.BadRequest.class, ReadTimeoutException.class}, maxAttempts = 2) public ResponseEntity<String> testMethod() throws Exception { try { return restTemplateRtp.exchange("http://localhost:8080/api/test", HttpMethod.POST, request, String.class); } catch (ResourceAccessException e) { Throwable rootCause = e.getCause(); if (rootCause instanceof SocketTimeoutException) { // 读取超时,抛出标记性异常,触发不重试 throw new ReadTimeoutException(rootCause); } else if (rootCause instanceof java.net.ConnectTimeoutException) { // 连接超时,抛出标记性异常,触发重试 throw new ConnectTimeoutException(rootCause); } // 其他IO异常,原样抛出 throw e; } }
方法三:使用@Retryable的exceptionExpression(Spring Retry 1.3+)
利用SpEL表达式直接检查异常的嵌套情况,无需额外代码:
@Retryable(value = {RestClientException.class}, exclude = {HttpClientErrorException.BadRequest.class}, maxAttempts = 2, exceptionExpression = "#{!(root.cause instanceof T(java.net.SocketTimeoutException))}") public ResponseEntity<String> testMethod() throws Exception { return restTemplateRtp.exchange("http://localhost:8080/api/test", HttpMethod.POST, request, String.class); }
表达式含义:当异常的根因是SocketTimeoutException时,不执行重试;其他符合条件的异常正常触发重试。
内容的提问来源于stack exchange,提问作者Guru
相关产品推荐
相关产品推荐

