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();
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐