微服务架构项目开发中,API接口都统一使用响应结构。
   
   http状态码统一返回200,状态码根据结构体的code获取。
  
    
  
   "code"
  
  
   :
  
  
   0
  
  
   ,
  
  
   "message"
  
  
   :
  
  
   "success"
  
  
   ,
  
  
   "data"
  
  
   :
  
  
   {
   
  
  
   "name"
  
  
   :
  
  
   "kent"
  
  
   用户请求时,服务调用流程。
   
    
  
  
   微服务架构中,在正常的情况下,返回的数据结构是按照响应结构体返回的,但服务调用发生异常时,却返回不了code。
   
   例子,在order-service调用product-service,由于库存不足,抛出异常,返回的结果如下:
  
  
   "timestamp"
  
  
   :
  
  
   "2020-08-11 13:25:03"
  
  
   ,
  
  
   "status"
  
  
   :
  
  
   500
  
  
   ,
  
  
   "error"
  
  
   :
  
  
   "Internal Server Error"
  
  
   ,
  
  
   "exception"
  
  
   :
  
  
   "tech.xproject.common.core.exception.BusinessException"
  
  
   ,
  
  
   "message"
  
  
   :
  
  
   "not enough stock"
  
  
   ,
  
  
   "trace"
  
  
   :
  
  
   "tech.xproject.common.core.exception.BusinessException: not enough stock"
  
  
   自定义
   
    FeignErrorDecoder
   
   、
   
    DefaultErrorAttributes
   
   、
   
    BusinessException
   
   对异常进行处理。
  
  
  
   
    代码位置:service-api
   
  
  package tech.xproject.order.config;
import com.alibaba.fastjson.JSON;
import feign.FeignException;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import tech.xproject.common.core.entity.ExceptionInfo;
import tech.xproject.common.core.enums.ResultCodeEnum;
import tech.xproject.common.core.exception.BusinessException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
 * @author kent
@Slf4j
@Configuration
public class FeignErrorDecoder extends ErrorDecoder.Default {
    @Override
    public Exception decode(String methodKey, Response response) {
        Exception exception = super.decode(methodKey, response);
        
        if (exception instanceof RetryableException) {
            return exception;
        try {
            
            if (exception instanceof FeignException && ((FeignException) exception).responseBody().isPresent()) {
                ByteBuffer responseBody = ((FeignException) exception).responseBody().get();
                String bodyText = StandardCharsets.UTF_8.newDecoder().decode(responseBody.asReadOnlyBuffer()).toString();
                
                ExceptionInfo exceptionInfo = JSON.parseObject(bodyText, ExceptionInfo.class);
                
                Integer code = Optional.ofNullable(exceptionInfo.getCode()).orElse(ResultCodeEnum.ERROR.getCode());
                
                String message = Optional.ofNullable(exceptionInfo.getMessage()).orElse(ResultCodeEnum.ERROR.getMessage());
                return new BusinessException(code, message);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        return exception;
 
代码位置:service-api
 
@FeignClient(name = ServiceNameConstant.ORDER_SERVICE, configuration = {FeignErrorDecoder.class})
完整代码示例
 
package tech.xproject.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import tech.xproject.common.core.constant.ServiceNameConstant;
import tech.xproject.order.config.FeignErrorDecoder;
import tech.xproject.order.pojo.dto.CreateOrderReqDTO;
import tech.xproject.order.pojo.entity.Order;
 * @author kent
@FeignClient(name = ServiceNameConstant.ORDER_SERVICE, configuration = {FeignErrorDecoder.class})
public interface RemoteOrderService {
     * 创建订单
     * @param createOrderReqDTO createOrderReqDTO
     * @return Order
    @PostMapping
    
("/order/create")
    Order create(@RequestBody CreateOrderReqDTO createOrderReqDTO);
 
代码位置:service
 若不自定义DefaultErrorAttributes,在返回时并不会带上code,需要将自定义的参数加入返回的对象中
 
package tech.xproject.product.handler;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import tech.xproject.common.core.exception.BusinessException;
import java.util.Map;
 * @author kent
@Component
@Primary
public class CustomErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
        Throwable error = this.getError(webRequest);
        if (error instanceof BusinessException) {
            errorAttributes.put("code", ((BusinessException) error).getCode());
        return errorAttributes;
 
代码位置:web
 
package tech.xproject.web.manager.handler;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import tech.xproject.common.core.entity.R;
import tech.xproject.common.core.enums.ResultCodeEnum;
import tech.xproject.common.core.exception.BusinessException;
import java.util.List;
 * @author kent
@Slf4j
@RestControllerAdvice
public class WebGlobalExceptionHandler {
    @ExceptionHandler({FeignException.class})
    @ResponseBody
    public R<?> feignExceptionHandler(FeignException exception) {
        log.error(exception.getMessage(), exception);
        return R.error(exception.getMessage());
    @ExceptionHandler({RuntimeException.class})
    @ResponseBody
    public R<?> runtimeExceptionHandler(RuntimeException exception) {
        log.error(exception.getMessage(), exception);
        return R.error(exception.getMessage());
    @ExceptionHandler({Exception.class})
    @ResponseBody
    public R<?> exceptionHandler(Exception exception) {
        log.error(exception.getMessage(), exception);
        return R.error(exception.getMessage());
    @ExceptionHandler({BusinessException.class})
    public R<?> businessExceptionHandler(BusinessException exception) {
        log.error(exception.getMessage(), exception);
        return R.error(exception.getCode(), exception.getMessage());
    @ExceptionHandler({BindException.class})
    @ResponseBody
    public R<?> bindExceptionHandler(BindException exception) {
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        String errorMessage = fieldErrors.get(0).getDefaultMessage();
        log.error(errorMessage, exception);
        return R.
    
error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage);
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R<?> validateExceptionHandler(MethodArgumentNotValidException exception) {
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        String errorMessage = fieldErrors.get(0).getDefaultMessage();
        log.error(errorMessage, exception);
        return R.error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage);
 
代码位置:gateway
 
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
    
    JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
            this.resourceProperties, this.serverProperties.getError(), this.applicationContext);
    exceptionHandler.setViewResolvers(this.viewResolvers);
    exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
    exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
    return exceptionHandler;
完整代码示例
 
package tech.xproject.gateway.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import tech.xproject.gateway.handler.JsonExceptionHandler;
import java.util.Collections;
import java.util.List;
 * @author kent
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {
    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public ErrorHandlerConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
                this.resourceProperties, this.serverProperties.getError(), this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
 
代码位置:gateway
 
使用自定义的结构体返回,code、message、data
 
 * get error attributes
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
    Throwable error = super.getError(request);
    String exMessage = error != null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage();
    String message = String.format("request error [%s %s],exception:%s", request.methodName(), request.uri(), exMessage);
    Map<String, Object> map = new HashMap<>(3);
    map.put("code", ResultCodeEnum.ERROR.getCode());
    map.put("message", message);
    map.put("data", null);
    return map;
重写getHttpStatus方法,返回http状态码200
 
 * response http code 200
 * the error code need use the code in response content
 * @param errorAttributes
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
    return HttpStatus.OK.value();
完整代码示例
 
package tech.xproject.gateway.handler;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import tech.xproject.common.core.enums.ResultCodeEnum;
import java.util.HashMap;
import java.util.Map;
 * @author kent
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
     * get error attributes
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Throwable error = super.getError(request);
        String exMessage = error != null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage();
        String message = String.format("request error [%s %s],exception:%s", request.methodName(), request.uri(), exMessage);
        Map<String, Object> map = new HashMap<>(3);
        map.put("code", ResultCodeEnum.ERROR.getCode());
        map.put("message", message);
        map.put("data", null);
        return map;
     * render with json
     * @param errorAttributes
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
     * response http code 200
     * the error code need use the code in response content
     * @param errorAttributes
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return HttpStatus.OK.value();
在网上查阅各类文章,始终没找到解决方案,只有各类的零散的解决方式,最后通过自己翻代码,断点调试,并结合相关的文章终于解决了。
 使用文章跟代码解决方式记录下来,给有需要的人提供参考。
 
 
feign-custom-exception-code-demo
 
这时候feign的接收对象可以使用该对象。
现在接口都追求rest风格,接口在正常流程时会返回请求的数据,错误时会返回错误的描述信息。此时不同的情况下httpcode也不能统一是200
那么正常和异常的情况就...
当接口返回值interface的时候 如jpa的page Page<User>,feign调用报错
其它类似的错误 也可以利用这种方式 画葫芦
使用于jpa的page直接序列化/反序列化出现异常错误的问题。
当Jackson返回如下异常的时候 可以使用这种方式去修复 支持不修改任何代码 只新增
Can not construct instance of org.springframework.data.domain.Page: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
    <dependency>
      <groupI
                                    ErrorDecoder-错误解码器的自定义
Feign 的默认错误处理程序ErrorDecoder.default总是抛出FeignException。
现在,这种行为并不总是最有用的。因此,要自定义抛出的异常,我们可以使用*CustomErrorDecoder*:
public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Res
如果不使用异常,那么就必须在调用点检查特定的错误,并在程序的很多地方去处理它;如果使用异常,那么就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。因此...
                                    上篇文章介绍了Feign的编码器Encoder,本篇继续了解它的解码器`Decoder`,以及错误解码器`ErrorDecoder`。
编码器作用于Request,那么解码器作用于Response,用于解析Http请求的响应,提取有用信息数据。
在微服务开发当中使用了Feign进行服务之间的调用,在正常情况下是可以以响应的格式返回对象数据,但是一旦发生feign客户端的异常错误,每个下游系统的异常返回方式不同,需要编写大量的错误处理代码来适应不同的服务,而且错误处理代码混在业务代码中,违反单一职责原则和最少知识原则。面临着维护难度上升的风险。需要一个方案来规避这些后期维护成本上升的风险。
为了防止项目腐化,避免错误处理代码与业务代码强耦合。导致后期的维护成本的上升和陷入逻辑迷宫的风险。保证灵活性和高可维护性
拆分错误处理代码和业务
                                    近期在使用Feign开发调用其他系统的微服务接口的时候遇到了需求,需要把原来系统抛出来的状态码和异常捕捉以及原样抛出。由于使用了Hystrix熔断,发现Feign返回的异常和原来抛出的不一致,故在此做个解决记录
解决方案:
一、自定义全局异常配置
@Configuration
public class FeignGlobalConfig {
  @Bean
  ErrorDecoder responseErrorDecoder() {
    // 调用异常处理
    return new Defa.