springboot(4.5)springcloud远程调用异常统一处理

概述

在目前微服务流行的年代,稍微大点的项目都会使用微服务架构模式。本文主要记录springcloud远程调用返回结果的异常统一处理。

应用篇

假设微服务的返回结果有如下封装:

@Data
public class ApiResponse<T> {
    /**
     * 返回码 0 为成功 其他为异常
     */
    private int code;
    /**
     * 异常提示信息
     */
    private String message;
    /**
     * 返回的数据
     */
    private T result;
}

在使用微服务进行远程调用后得到ApiResponse实例,都需要判断返回结果的code,如果是成功,则继续后续处理,如果是失败,则抛出异常,终止程序向下运行,还有可能需要回滚事务(分布式事务这里不做介绍),那么有没有办法在框架层面来完成这个操作呢?

答案是肯定的。(假设微服务的返回统一为json格式)

springcloud远程调用统一返回处理

代码如下:

@Component
public class MyResponseEntityDecoder implements Decoder,SmartInitializingSingleton {
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    private ResponseEntityDecoder responseEntityDecoder;

    @Override
    public Object decode(final Response response, Type type) throws IOException,
            FeignException {
        //返回码不为200,表示远程访问有运行时异常,则直接抛出异常即可
        if (response.status() ==200) {
            //充分利用spring框架提供的解析器
            Object result = responseEntityDecoder.decode(response, type);
            ApiResponse baseResponse = (ApiResponse)result;
            int code = baseResponse.getCode();
            if (code==0){
                return baseResponse;
            }
            throw new RuntimeException("异常返回");
        }
        throw new RuntimeException("异常返回");
    }

    @Override
    public void afterSingletonsInstantiated() {
        //初始化spring提供的解析器
        responseEntityDecoder = new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }
}

在Feign进行远程调用后,需要对结果进行解码成具体的Java对象,如ApiResponse对象。而解码操作的类,必须实现Decoder接口,并重新decode方法,最后需要让其注入到spring的bean工厂中。
通过查看springcloud源代码,发现是一个叫ResponseEntityDecoder的解析器进行解码的(ResponseEntityDecoder也实现了Decoder接口),为了充分利用这个解析器,上述自定义的解析器对ResponseEntityDecoder进行了适配。

请求端代码如下

@RequestMapping("/client")
@Controller
public class ClientCtroller {
    private transient Logger log = LoggerFactory.getLogger(this.getClass());
    @Resource
    private UserService userService;

    @PostMapping("api1")
    @ResponseBody
    public ApiResponse<UserDto> api1(@RequestBody UserDto UserDto) {
        ApiResponse<UserDto> dt = userService.api1(UserDto);
        return dt;
    }

    @PostMapping("api2")
    @ResponseBody
    public ApiResponse<List<UserDto>> callbackApi2(@RequestBody UserDto UserDto) {
        ApiResponse<List<UserDto>> dt = userService.api2(UserDto);
        return dt;
    }
}

接口代码

@FeignClient(name = "userService", url = "${server.service.url:}", path = "/server")
public interface UserService {
    @PostMapping("api1")
    ApiResponse<UserDto> api1(@RequestBody UserDto UserDto);

    @PostMapping("api2")
    ApiResponse<List<UserDto>> api2(@RequestBody UserDto UserDto);
}

服务端代码

@RequestMapping("/server")
@Controller
public class ServerController {
    @PostMapping("api1")
    @ResponseBody
    public ApiResponse<UserDto> api1(@RequestBody UserDto UserDto) {
        ApiResponse<UserDto> dt = new ApiResponse<>();
        dt.setCode(0);
        dt.setMessage("successs");
        dt.setResult(UserDto);
        return dt;
    }

    @PostMapping("api2")
    @ResponseBody
    public ApiResponse<List<UserDto>> api2(@RequestBody UserDto UserDto) {
        ApiResponse<List<UserDto>> dt = new ApiResponse<>();
        dt.setCode(0);
        dt.setMessage("successs");
        List<UserDto> list = new ArrayList<>();
        list.add(UserDto);
        list.add(UserDto);
        dt.setResult(list);
        return dt;
    }
}

其他代码

@Data
public class UserDto {
    private int id;

    private String name;

    private String pwd;
}

注意事项

既然ApiResponse是一个泛型类,那么在系统中所有的地方,只要用到了ApiResponse类,都要使用泛型,否则在客户端获取到的数据中,ApiResponse类的result属性值就可能不是泛型对应的实际类型。

源码解析篇

对于我们自定义的被@FeignClient修饰的接口,在spring启动的时候,将会被扫描并为其生成一个代理对象,并将代理对象注入到spring的bean工厂中。
动态代理类为FeignInvocationHandler,源码如下:

static class FeignInvocationHandler implements InvocationHandler {
    //目标对象,即被@FeignClient修饰的接口
    private final Target target;
    //接口的方法和方法处理器 MethodHandler为SynchronousMethodHandler类型,负责真正做事
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object
              otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
      return dispatch.get(method).invoke(args);
    }
}

构造SynchronousMethodHandler类时,dispatch的value为MethodHandler类型(实际上是SynchronousMethodHandler类型),SynchronousMethodHandler对象成创建比较复杂,这里暂不做介绍。

当我们调用被@FeignClient修饰的接口时,会执行代码dispatch.get(method).invoke(args);即会执行SynchronousMethodHandler类的invoke方法,具体代码如下:

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

然后会执行executeAndDecode方法,这个方法代码较长,主要有如下2个功能点:

  • 请求远程接口,默认为HttpURLConnection进行远程接口访问
  • 返回解析结果,默认为ResponseEntityDecoder,如果要使用自定义的,必须自定义类实现Decoder接口,重写相关方法,并注入到spring工厂。如我们自定义的解码器MyResponseEntityDecoder

为了清楚的阅读源码,下述代码删除了次要的部分:

Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    Response response;
    long start = System.nanoTime();
    try {
      //请求远程接口,默认为HttpURLConnection进行远程接口访问
      response = client.execute(request, options);
    } catch (IOException e) {
      throw errorExecuting(request, e);
    }
    
    boolean shouldClose = true;
    try {
      // 返回结果的处理
      if (logLevel != Logger.Level.NONE) {
        response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
                response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404) {
        return decode(response);
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值