ErrorDecoder + InvocationHandlerFactory 的方式,实现 Feign 遇到 400+/500+ 时不抛异常,而是将响应体解析为 Response返回
·
ErrorDecoder + InvocationHandlerFactory 的方式,实现 Feign 遇到 400+/500+ 时不抛异常,而是将响应体解析为 Response返回
核心目标
✅ Feign 不抛 FeignException
✅ 400 / 500 返回 完整 Response 对象
✅ 业务代码保持:
FeignConfig(核心)
package com.xxx.feign.config;
import com.xxx.feign.handler.BaseInvocationHandler;
import feign.Feign;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
/**
* 自定义 ErrorDecoder:非 2xx 统一转为 InternalClientException
*/
@Bean
public ErrorDecoder errorDecoder() {
return new ErrorDecoder() {
@Override
public Exception decode(String methodKey, feign.Response response) {
int status = response.status();
if (status >= 400) {
String body = "{}";
try {
body = feign.Util.toString(response.body().asReader());
} catch (Exception ignored) {
}
return new com.xxx.feign.exception.InternalClientException(
status,
methodKey,
body
);
}
return null;
}
};
}
/**
* 禁用重试(可按需调整)
*/
@Bean
public Retryer retryer() {
return Retryer.NEVER_RETRY;
}
/**
* 使用自定义 InvocationHandler
*/
@Bean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder()
.retryer(retryer)
.invocationHandlerFactory(
(target, dispatch) ->
new BaseInvocationHandler(target, dispatch)
);
}
}
InternalClientException(承载异常信息)
package com.xxx.feign.exception;
public class InternalClientException extends Exception {
private final int responseStatus;
private final String methodKey;
private final String body;
public InternalClientException(int responseStatus, String methodKey, String body) {
super(body);
this.responseStatus = responseStatus;
this.methodKey = methodKey;
this.body = body;
}
public int getResponseStatus() {
return responseStatus;
}
public String getMethodKey() {
return methodKey;
}
public String getBody() {
return body;
}
}
BaseInvocationHandler(关键)
package com.xxx.feign.handler;
import com.xxx.feign.decoder.ResponseStatusDecoder;
import com.xxx.feign.exception.InternalClientException;
import com.xxx.feign.util.JacksonUtils;
import feign.InvocationHandler;
import feign.MethodHandler;
import feign.Target;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Map;
public class BaseInvocationHandler implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(BaseInvocationHandler.class);
private final Target<?> target;
private final Map<Method, MethodHandler> dispatch;
public BaseInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch) {
this.target = target;
this.dispatch = dispatch;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
MethodHandler handler = dispatch.get(method);
try {
return handler.invoke(args);
} catch (Exception ex) {
return onException(method, args, ex);
}
}
protected Object onException(Method method, Object[] args, Exception ex) throws Exception {
if (ex instanceof InternalClientException) {
return onClientInternalException(method, args, (InternalClientException) ex);
}
throw ex;
}
protected Object onClientInternalException(
Method method,
Object[] args,
InternalClientException ex) {
log.trace("Feign client error, status={}", ex.getResponseStatus(), ex);
Class<?> returnType = method.getReturnType();
if (returnType == void.class) {
throw new RuntimeException(
"Feign call failed, status=" + ex.getResponseStatus()
);
}
try {
// 将错误响应体解析为 Response<T>
Object result = JacksonUtils.parse(ex.getBody(), returnType);
// 将 HTTP 状态码写入 Response 对象
ResponseStatusDecoder.setResponseStatus(result, ex.getResponseStatus());
return result;
} catch (Exception e) {
throw new RuntimeException(
"Failed to parse feign error response", e
);
}
}
}
ResponseStatusDecoder(注入 HTTP 状态码)
package com.xxx.feign.decoder;
import java.lang.reflect.Field;
public class ResponseStatusDecoder {
public static void setResponseStatus(Object response, int httpStatus) {
if (response == null) {
return;
}
try {
Field field = response.getClass().getDeclaredField("httpStatus");
field.setAccessible(true);
field.set(response, httpStatus);
} catch (Exception ignored) {
// 没有该字段也没关系
}
}
}
Response 示例
public class Response<T> {
private int code;
private String msg;
private T data;
private int httpStatus; // 可选
public boolean isSuccess() {
return code == 0 || code == 200;
}
// getter / setter
}
Feign Client 使用示例
@FeignClient(
name = "ticket-service",
configuration = FeignConfig.class
)
public interface TicketFeignClient {
@PostMapping("/related-ticket-auth/check")
Response<Boolean> checkRelateTicketAuth(
@RequestBody RelateTicketAuthCheckForm form
);
default Boolean checkRelateTicketAuth(String main, String related) {
RelateTicketAuthCheckForm form = new RelateTicketAuthCheckForm();
form.setMainTicketNo(main);
form.setRelatedTicketNo(related);
Response<Boolean> response = checkRelateTicketAuth(form);
ITSRErrors.CLIENT_ERROR.whenFailed(response);
return response.getData();
}
}
更多推荐




所有评论(0)