简易原理图

原理基于请求头传递错误消息,利用aop和全局异常拦截机制实现。

服务提供者

远程调用本地方法b,throw异常出来FeignExceptionAspect AOP拦截处理异常到请求头中,继续throwGlobalExceptionHandler处理,返回响应ResponseVo

服务消费者

controller方法中的远程调用完毕(异常),被FeignExceptionClient(自定义feign调用处理器)检测到请求头中错误码和错误信息,转化为本地BusinessException throw出来FeignExceptionAspect AOP拦截处理异常到请求头中,继续throwGlobalExceptionHandler处理,返回响应ResponseVo

上述异常处理机制与使用Decoder处理的异同优劣

上述异常处理机制代码简洁程度无法跟解码器处理方式比较,但是处理的细致,可以处理更多自定义的异常,比如:UserLoseException上述处理机制不受限于原生feign的机制,解码器在处理void的时候会出现无法生效情况,并且通过寻找其他博文在低版本中可以通过反射修改强制属性执行解码器的方式得到解决,3.0.5版本不支持这种操作。 博文链接如下:https://blog.csdn.net/m0_37298252/article/details/133690069ErrorDecoder生效处理http status 非200的

总结

feign本身不提供异常处理的方案,但是提供入参返参的编解码机制,有上述的分析我们知道Decoder生效必须是有返回值的方法,而不是void,所以全局微服务接口规范为必须有返参或者统一返回值即可。基于AOP的处理方案,前提是方法必须真正执行到,比如说MVC处理直接抛错,还没进入controller方法,则无法正确抛出错误信息,比如:404或者missig body等错误。如果出现这种情况,需要在全局异常拦截时特殊处理下。

基于AOP和自定义feign执行器代码实现

/**

* 全局异常处理器

*/

@RestControllerAdvice

@Configuration

@Slf4j

public class GlobalExceptionHandler {

@ExceptionHandler(value = Exception.class)

public Object defaultErrorHandler(Exception e) throws Exception {

log.error("系统异常:{}", e);

return ResultUtils.error("系统异常", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));

}

/**

* 处理运行时异常

*

* @param ex

* @return

*/

@ExceptionHandler(value = RuntimeException.class)

public Object runtimeException(RuntimeException ex) {

log.error("系统异常:{}", ex);

return ResultUtils.error("系统异常!", String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));

}

@ExceptionHandler(value = DecodeException.class)

public Object decodeException(DecodeException ex) {

log.error("系统异常:{}", ex);

return ResultUtils.error(ex.getMessage(), String.valueOf(ex.status()));

}

/**

* 处理自定义业务异常

*

* @param ex

* @return

*/

@ExceptionHandler(value = BusinessException.class)

public Object exceptionHandler(BusinessException ex) {

log.error("业务异常详情:{}", ex);

return ResultUtils.error(ex.getMessage(), ex.getCode());

}

@ExceptionHandler(value = UserContextLoseException.class)

public Object notLoginException(UserContextLoseException e) {

log.error("当前用户信息不存在异常:{}", e);

return ResultUtils.notLogin();

}

/**

* 方法入参校验异常处理 content-type!=application/json

*

* @param ex 异常

* @return Object

*/

@ExceptionHandler({BindException.class})

public Object bindExceptionException(BindException ex) {

List validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());

log.warn("参数非法:{}", ex);

return ResultUtils.error(validResult.get(0));

}

/**

* 方法入参校验异常处理 content-type=application/json

*

* @param ex 异常

* @return Object

*/

@ExceptionHandler(value = MethodArgumentNotValidException.class)

public Object methodArgumentNotValidException(MethodArgumentNotValidException ex) {

List validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());

log.warn("参数非法:{}", ex);

// TODO 需要特殊处理异常,否则微服务接口被@Validated校验未通过时无法抛出对应的异常信息

return ResultUtils.error(validResult.get(0));

}

/**

* 请求类型不支持

*

* @param httpRequestMethodNotSupportedException

* @return

*/

@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)

@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)

public Object handleNotSupportedHttpMethodException(HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {

log.error("HttpRequestMethodNotSupportedException:{}", httpRequestMethodNotSupportedException);

return ResultUtils.error(httpRequestMethodNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));

}

/**

* media type not support

*

* @param httpMediaTypeNotSupportedException

* @return

*/

@ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)

@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)

public Object handleNotSupportedHttpMethodException(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {

log.error("HttpMediaTypeNotSupportedException:{}", httpMediaTypeNotSupportedException);

return ResultUtils.error(httpMediaTypeNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));

}

}

@Slf4j

@Aspect

@Order(value = 100)

@Component

public class FeignExceptionAspect {

@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +

" || @annotation(org.springframework.web.bind.annotation.GetMapping)" +

" || @annotation(org.springframework.web.bind.annotation.PostMapping)" +

" || @annotation(org.springframework.web.bind.annotation.PutMapping)" +

" || @annotation(org.springframework.web.bind.annotation.DeleteMapping)")

public void pointcut() {

}

@Around("pointcut()")

public Object around(ProceedingJoinPoint joinPoint) {

try {

Object proceed = joinPoint.proceed();

return proceed;

} catch (BusinessException e) {

log.error("feign调用异常:{}", e.getMessage());

if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {

HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));

}

throw e;

} catch (UserContextLoseException e) {

log.error("用户信息缺失:{}", e);

if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {

HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));

}

throw e;

} catch (FeignException e) {

// 存在未发起远程调用前抛出的FeignException异常

if (e instanceof FeignException.ServiceUnavailable) {

FeignException.ServiceUnavailable serviceUnavailable = (FeignException.ServiceUnavailable) e;

log.error(serviceUnavailable.getMessage());

throw BusinessException.createException("服务不可用");

}

throw e;

} catch (Throwable throwable) {

Throwable cause = throwable.getCause();

while (null != cause && null != cause.getCause()) {

cause = cause.getCause();

}

if (cause instanceof BusinessException) {

if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {

HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((BusinessException) cause).getCode());

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));

}

throw (BusinessException) cause;

} else if (cause instanceof UserContextLoseException) {

if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {

HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((UserContextLoseException) cause).getCode());

response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));

}

throw (UserContextLoseException) cause;

}

log.error("接口调用异常:{}", throwable);

throw new RuntimeException("未知异常");

}

}

}

@Slf4j

public class FeignExceptionClient implements Client {

@Override

public Response execute(Request request, Request.Options options) throws IOException {

Response response = new ApacheHttpClient().execute(request, options);

RequestTemplate requestTemplate = response.request().requestTemplate();

Target target = requestTemplate.feignTarget();

String serviceName = target.name();

Class type = target.type();

String api = type.getName();

String url = requestTemplate.url();

Collection errorCodes = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY);

Collection errormessage = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY);

String errorMessage = null;

if (null != errormessage && !errormessage.isEmpty()) {

errorMessage = (String) ((List) errormessage).get(0);

errorMessage = Base64.decodeStr(errorMessage, CommonConsts.UTF8);

}

if (CollectionUtils.isNotEmpty(errorCodes)) {

logInvokeError(serviceName, api, url);

Object errorCode = ((List) errorCodes).get(0);

if (ResultConsts.NOT_LOGIN.toString().equals(errorCode)) {

throw UserContextLoseException.createException();

}

if (String.valueOf(HttpStatus.EXPECTATION_FAILED.value()).equals(errorCode)) {

throw BusinessException.createException("系统异常");

}

if (String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()).equals(errorCode)) {

throw BusinessException.createException("系统异常");

}

if (StringUtils.isNotEmpty(errorMessage)) {

throw BusinessException.createException(errorMessage);

}

throw BusinessException.createException("系统异常");

}

if (StringUtils.isNotEmpty(errorMessage)) {

logInvokeError(serviceName, api, url);

throw BusinessException.createException(errorMessage);

}

return response;

}

private void logInvokeError(String serviceName, String api, String method) {

log.error("调用微服务[{}]-Api[{}]-Uri[{}]异常", serviceName, api, method);

}

}

基于Decoder处理

public class FeignDecoder implements Decoder {

@Override

public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {

String typeName = type.getTypeName();// class void

if (StringUtils.isNotEmpty(typeName)) {

Response.Body body = response.body();

String resultString = Util.toString(body.asReader(Util.UTF_8));

ResponseVO responseVO = JSONObject.parseObject(resultString, ResponseVO.class);

if (null != responseVO) {

if (null != responseVO.getCode() && !responseVO.getCode().toString().endsWith(String.valueOf(ResultConsts.SUCCESS_STATUS))) {

// 2002000100 417 not-login 100 business

throw new DecodeException(responseVO.getCode(), responseVO.getMessage(), response.request());

}

}

Class responseCls;

try {

responseCls = Class.forName(typeName);

} catch (ClassNotFoundException e) {

throw new RuntimeException(e);

}

return JSONObject.parseObject(resultString, responseCls);

}

return Util.emptyValueOf(type);

}

}

public class FeignErrorDecoder implements ErrorDecoder {

@Override

public Exception decode(String methodKey, Response response) {

return new ErrorDecoder.Default().decode(methodKey, response);

}

}

bean注入

@Bean

public Decoder decoder() {

return new FeignDecoder()::decode;

}

@Bean

public ErrorDecoder errorDecoder() {

return new FeignErrorDecoder()::decode;

}

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: