Feign的配置和使用
Feign的基础配置
- 引入jar包(配置类)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 测试调用接口
package com.itllc.api;
import com.itllc.api.fallback.CustomerClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 声明需要调用的微服务名称
* @FeignClient
* * value : 服务提供者的名称
* * fallback : 配置熔断发生降级方法
* 实现类
*/
@FeignClient(value = "shop-customer",fallback = CustomerClientFallback.class)
public interface CustomerClient {
@GetMapping("/customer-anon/one")
String getTest();
}
- 熔断类
package com.itllc.api.fallback;
import com.itllc.api.CustomerClient;
import org.springframework.stereotype.Component;
@Component //注意注解的添加不然找不到熔断类
public class CustomerClientFallback implements CustomerClient {
@Override
public String getTest() {
return "测试失败";
}
}
- feign接口实现类
package com.itllc.fallback;
import com.itllc.api.CustomerClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
@RestController // 注意注解的添加不然会报404
public class BackCustomerImpl implements CustomerClient {
@Override
public String getTest() {
System.out.println("1111111111111111111111111111111");
int p=1/0;
return "测试成功";
}
}
注意这里的实现类必须使用 @RestController 注解不然会有问题,有的框架是不会报错
- 测试
@Resource
private CustomerClient cusClient;
@Override
public String findByUsernameTest() {
String test = cusClient.getTest();
return test;
}
注意启动类上加上@EnableFeignClients,@EnableDiscoveryClient(如果是nacos配置服务发现可以不加)注解
因为实现类中存在错误,启动了熔断机制,所以是测试失败!!!!
特别注意引入feign的jar包时注意下jar版本的一致性不然回报如下错误
Feign远程调用报错:java.lang.NoSuchMethodError: feign.Request.requestTemplate()Lfeign/RequestTemplate;
解决方法将版本修改一致就可以了
Feign配置使feign调用时将异常抛出到调用模块
1、结果集配置
import com.itllc.OpenFigenError.constants.BaseExceptionCode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @since 2020-05-07 16:06
* @description 规定成功响应此结果
**/
@Data
@ApiModel(value = "结果集", description = "结果集")
@NoArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = -8621900780393672869L;
public Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(T data) {
this.data = data;
}
public static Result success() {
return new Result<>();
}
public static Result success(Object data) {
return new Result<>(data);
}
public static Result<String> failed(Integer code, String message) {
return new Result<>(code, message);
}
public static Result failed(BaseExceptionCode exceptionCode) {
return new Result<>(exceptionCode.getCode(), exceptionCode.getMessage());
}
/**
* @since 2020-06-03 10:15
* @description 200:代表此次请求成功, 其他请参考错误代码信息表
**/
@ApiModelProperty(value = "响应码", example = "200")
private int code = 200;
/**
* @since 2020-06-03 10:16
* @description 错误信息
**/
@ApiModelProperty(value = "错误信息", example = "SUCCESS")
private String msg = "SUCCESS";
/**
* @since 2020-06-03 10:17
* @description 数据集
**/
@ApiModelProperty(position = 2, value = "数据集")
private T data;
}
import com.itllc.OpenFigenError.constants.BaseExceptionCode;
import com.itllc.OpenFigenError.constants.ExceptionCode;
import com.itllc.util.Result;
/**
* @since 2020-06-02 20:30
* 错误结果集
**/
public class Receipt extends Result<String> {
public static final Integer DEFAULT_STATUS = 200;
private static final long serialVersionUID = -7082264053494083998L;
public Receipt(int status, String message) {
super(status, message);
}
public static Receipt failed(BaseExceptionCode exceptionCode) {
return new Receipt(exceptionCode.getCode(), exceptionCode.getMessage());
}
public Receipt(String message) {
ExceptionCode success = ExceptionCode.SUCCESS;
new Receipt(success.getCode(), message);
}
public Receipt() {}
}
2、openFeign的详细配置
public interface BaseException {
/**
* @return 返回异常信息
*/
String getMessage();
/**
* @return 返回异常编码
*/
int getCode();
}
import com.itllc.OpenFigenError.domain.Receipt;
import com.itllc.util.Result;
import com.netflix.hystrix.exception.HystrixBadRequestException;
/**
* @description 非运行期异常基类,所有自定义非运行时异常继承该类
* @since 2020-04-07 20:21
*/
public class BaseUncheckedException extends HystrixBadRequestException implements BaseException {
private static final long serialVersionUID = 778887391066124051L;
private int status = 400;
private int code = 200;
private String message;
public BaseUncheckedException(String message) {
super(message);
}
public BaseUncheckedException(int code, String message) {
super(message);
this.code = code;
}
public static BaseUncheckedException getInstance(Result<String> receipt) {
BaseUncheckedException hystrixException = new BaseUncheckedException(receipt.getCode(), receipt.getMsg());
// hystrixException.setStatus(200);
return hystrixException;
}
public Receipt getExceptionResult() {
return new Receipt(getCode(), getMessage());
}
@Override
public String getMessage() {
return super.getMessage();
}
@Override
public int getCode() {
return code;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public void setCode(int code) {
this.code = code;
}
public void setMessage(String message) {
this.message = message;
}
}
import com.itllc.OpenFigenError.constants.BaseExceptionCode;
import java.io.Serializable;
/**
* @since 2020-04-07 10:56
* @description 处理业务逻辑时,进行抛出的异常。
*/
public class BusinessException extends BaseUncheckedException implements Serializable {
private static final long serialVersionUID = 5481596588955034193L;
public BusinessException(String message) {
super(message);
}
public BusinessException(Integer code, String message) {
super(code, message);
}
public static BusinessException busExp(BaseExceptionCode ex) {
return new BusinessException(ex.getCode(), ex.getMessage());
}
public static BusinessException busExp(String message) {
return new BusinessException(message);
}
@Override
public String toString() {
return "BusinessException [message=" + getMessage() + ", code=" + getCode() + "]";
}
}
import com.itllc.OpenFigenError.constants.ExceptionCode;
import com.itllc.util.Result;
import java.io.Serializable;
/**
* @since 2020-04-07 10:56
* @description 账号绑定异常
*/
public class AccountBindException extends BaseUncheckedException implements Serializable {
private static final long serialVersionUID = -9181249577310128445L;
private final String data;
public AccountBindException(String token) {
super(ExceptionCode.ACCOUNT_BIND.getCode(), ExceptionCode.ACCOUNT_BIND.getMessage());
this.data = token;
}
public Result<String> failed() {
Result<String> failed = Result.failed(getCode(), getMessage());
failed.setData(data);
return failed;
}
@Override
public String toString() {
return "AccountBindException [message=" + getMessage() + ", code=" + getCode() + ", token=" + data + "]";
}
}
/**
* @since 2020-04-07 20:18
*
* 00-通用业务 01-授权 02-对象存储 03-通知 04-日志 05-会员 06-动态 07-即时通讯
*
*/
public interface BaseExceptionCode {
/**
* @return 异常编码
*/
int getCode();
/**
* @return 异常消息
*/
String getMessage();
}
/**
* 系统错误码为六位,1位系统模块码(1表示系统通用,2表示业务)+ 2位具体业务码 + 3位自定义错误码(从001开始),各业务错误码在各自中心定义。
* eg: 100001-请求参数类型异常 301001-登陆用户不存在
*
**/
public enum ExceptionCode implements BaseExceptionCode {
// 10 系统错误码
SYS_MEDIA_TYPE_EX(100401, "请求参数类型异常"),
SYS_SQL_EX(100402, "运行SQL出现异常"),
SYS_NULL_POINT_EX(100003, "空指针异常"),
SYS_SYNTAX_ERROR(100404,"请求参数不匹配"),
SYS_UNAUTHENTICATED(100405,"未经授权"),
SYS_NOT_EXIST400(100406,"请求参数类型匹配错误"),
SYS_ACCESS_DENIED(100407,"服务器拒绝访问"),
SYS_NOT_EXIST(100408,"没有找到资源"),
SYS_NOT_EXIST405(100409,"不支持当前请求类型"),
SYS_REQUEST_MODE_INCORRECT(100410,"请求方式不正确"),
SYS_FEIGN_ERROR(100411,"服务降级通知"),
SYS_OPERATION_FAIL(100412,"操作失败"),
SYS_NO_DEL(100413,"该数据不能删除"),
SYS_NO_DATA(100414,"数据不存在"),
SYS_REQUEST_TIMEOUT_OR_BUSINESS_CIRCUIT_BREAKER_OCCURS(100415, "请求超时或发生业务熔断"),
SYS_REQUEST_TIMEOUT(100416, "请求超时"),
SYS_PARSING_FAILS(100418, "JSON解析失败"),
SYS_DOCUMENT_DOES_NOT_MEET_THE_REQUIREMENTS(100419, "文件不符合要求"),
SYS_PARAMETERS_DO_NOT_MATCH_PLEASE_FILL_IN_THE_CORRECT_PATH(100420, "参数不匹配,请填写正确路径"),
SYS_REQUEST_PARAMETER_IS_INCORRECT(100421, "请求参数不正确"),
SYS_REQUEST_IS_WRONG (100422, "请求有误"),
SYS_DATA_BODY_CANNOT_BE_EMPTY (100423, "数据体不能为空"),
SYS_SERVER_OVERLOAD_OR_MAINTENANCE(100424,"服务器超负载或停机维护"),
SYS_ACCESS_DENIED_EXCEPTION(100425, "不允许访问"),
SYS_SERVICE_BUSY(400500, "服务繁忙"),
SYS_EXECUTING_REQUEST_ERROR(100500, "服务无响应"),
SYS_INTERNAL_SERVER_ERROR(100500, "sorry, unknown error has occurred!"),
// 20 正常业务码
SUCCESS(200,"SUCCESS"),
// 30 业务逻辑错误码
// 00-通用业务 01-授权 02-对象存储 03-通知 04-日志 05-会员
BIZ_NO_MORE(999999,"没有更多了"),
BIZ_NULL_POINTER_EXCEPTION(300000,"空指针"),
BIZ_SAVE_FAIL(300800,"保存失败"),
BIZ_DISTRIBUTE_TIME_FAIL(300801,"不在发布是在范围内"),
BIZ_OPERATION_FAIL(300802,"操作失败"),
BIZ_NO_DEL(300803,"该数据不能删除"),
BIZ_PARAM_WRONG(300804,"参数错误"),
BIZ_SN_WRONG(300805,"出入库单号错误"),
BIZ_SN_LIMIT(300806,"出入库单号超过最大限制"),
BIZ_QUOTA_LIMIT(30087,"发行量超过最大限制"),
// 40 数据库错误码
DB_NOT_FOUND(400001,"未查询到数据"),
DB_OPERATION_ERROR(400002,"操作失败"),
DB_SQL_ERROR(400003,"数据库操作异常"),
DB_SQL_DEL(400004,"数据已经删除"),
DB_SQL_NULL(400500,"数据搞丢了!"),
// 50 其他错误码
// 特殊错误码
ACCOUNT_BIND(202020, "账号请求绑定"),
;
private final int code;
private final String message;
ExceptionCode(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
/**
* 通过code返回枚举
*/
public static ExceptionCode parse(int code){
ExceptionCode[] values = values();
for (ExceptionCode error : values) {
if(error.getCode() == code){
return error;
}
}
// 如果没有这个返回结果那么我将默认返回 500 错误
return SYS_INTERNAL_SERVER_ERROR;
}
}
import com.alibaba.fastjson.JSONException;
import com.itllc.OpenFigenError.constants.ExceptionCode;
import com.itllc.OpenFigenError.domain.Receipt;
import com.itllc.OpenFigenError.exce.AccountBindException;
import com.itllc.OpenFigenError.exce.BaseUncheckedException;
import com.itllc.util.Result;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import feign.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description 全局异常处理器
**/
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {
/**
* 账号绑定单独处理
* @param ex 账号绑定异常
* @return 错误结果集
*/
@ExceptionHandler(AccountBindException.class)
public Result<String> handleRetryableException(AccountBindException ex) {
return ex.failed();
}
/**
* 系统业务异常,包含熔断异常处理
* @param ex 系统业务异常
* @return 错误结果集
*/
@ExceptionHandler(value = BaseUncheckedException.class)
public Receipt handleBaseException(BaseUncheckedException ex, HttpServletResponse response) {
response.setStatus(ex.getStatus());
return new Receipt(ex.getCode(), ex.getMessage());
}
/**
* 请求超时或发生业务熔断失败
* @return 错误结果集
*/
@ExceptionHandler(value = {HystrixRuntimeException.class})
public Receipt handleHystrixRuntimeException(HystrixRuntimeException ex, HttpServletResponse response) {
if (ex.getFallbackException().getCause().getCause() instanceof BaseUncheckedException) {
BaseUncheckedException baseEx = (BaseUncheckedException) ex.getFallbackException().getCause().getCause();
return handleBaseException(baseEx, response);
}
return Receipt.failed(ExceptionCode.SYS_REQUEST_TIMEOUT_OR_BUSINESS_CIRCUIT_BREAKER_OCCURS);
}
/**
* 重试失败
* @param ex 超时异常
* @return 错误结果集
*/
@ExceptionHandler(RetryableException.class)
public Receipt handleRetryableException(RetryableException ex) {
return Receipt.failed(ExceptionCode.SYS_REQUEST_TIMEOUT);
}
/**
* 重试失败
* @param ex 超时异常
* @return 错误结果集
*/
@ExceptionHandler(AccessDeniedException.class)
public Receipt accessDeniedException(AccessDeniedException ex) {
return Receipt.failed(ExceptionCode.SYS_ACCESS_DENIED_EXCEPTION);
}
/**
* JSON failure
* @param ex JSON异常
* @param request 请求
* @return 错误结果集
*/
@ExceptionHandler(value = JSONException.class)
public Receipt handleJSONException(JSONException ex, HttpServletRequest request) {
return Receipt.failed(ExceptionCode.SYS_PARSING_FAILS);
}
/**
* 系统异常处理
* @param ex 全局异常
* @return 错误结果集
*/
@ExceptionHandler(value = Exception.class)
public Receipt handleException(Exception ex) {
ex.printStackTrace();
return Receipt.failed(ExceptionCode.SYS_INTERNAL_SERVER_ERROR);
}
/**
* 文件上传不符合要求
* @param ex 文件异常
* @return 错误结果集
*/
@ExceptionHandler(value = {MultipartException.class})
public Receipt handleMultipartException(MultipartException ex) {
return Receipt.failed(ExceptionCode.SYS_DOCUMENT_DOES_NOT_MEET_THE_REQUIREMENTS);
}
/**
* 访问路径被篡改
* @param ex 参数异常
* @return 错误结果集
*/
@ExceptionHandler(value = {MethodArgumentTypeMismatchException.class})
public Receipt handleNumberFormatException(MethodArgumentTypeMismatchException ex) {
return Receipt.failed(ExceptionCode.SYS_PARAMETERS_DO_NOT_MATCH_PLEASE_FILL_IN_THE_CORRECT_PATH);
}
/**
* 数据绑定异常
* @param ex 参数异常
* @return 错误结果集
*/
@ExceptionHandler(value = {ServletRequestBindingException.class})
public Receipt handleServletRequestBindingException(ServletRequestBindingException ex) {
return Receipt.failed(ExceptionCode.SYS_REQUEST_PARAMETER_IS_INCORRECT);
}
/**
* 请求的方法不存在
* @return 错误结果集
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Receipt handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
return Receipt.failed(ExceptionCode.SYS_REQUEST_IS_WRONG);
}
/**
* 不支持的媒体类型
* @return 错误结果集
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Receipt handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
return Receipt.failed(ExceptionCode.SYS_REQUEST_IS_WRONG);
}
/**
* 错误的数字格式
* @param ex 数字格式异常
* @return 错误结果集
*/
@ExceptionHandler(NumberFormatException.class)
public Receipt handleNumberFormatException(NumberFormatException ex) {
return Receipt.failed(ExceptionCode.SYS_REQUEST_IS_WRONG);
}
/**
* 请求数据体为空
* @param ex 请求异常
* @return 错误结果集
*/
@ExceptionHandler(value = {HttpMessageNotReadableException.class})
public Receipt handleAccessDeniedException(HttpMessageNotReadableException ex) {
return Receipt.failed(ExceptionCode.SYS_DATA_BODY_CANNOT_BE_EMPTY);
}
/**
* 校验异常
* 处理 @PathVariable和@RequestParam 验证不通过抛出的异常
* @param ex 参数异常
* @return 错误结果集
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public Result handleConstraintViolationException(ConstraintViolationException ex) {
List<String> errorInformation = ex.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
return Result.failed(ExceptionCode.SYS_MEDIA_TYPE_EX.getCode(), errorInformation.get(0));
}
/**
* 校验异常
* @param ex 参数绑定异常
* @return 错误结果集
*/
@ExceptionHandler(value = BindException.class)
public Result handleBindException(BindException ex) {
return handleBaseBindAndMethodArgumentNotValidMessage(ex.getAllErrors());
}
/**
* 校验异常
* 处理 @RequestBody ,验证不通过抛出的异常
* @param ex 参数校验异常
* @return 错误结果集
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Receipt handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
/*
List<String> errorInformation = ex.getBindingResult().getAllErrors()
.stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
return Result.failed(ExceptionCode.INTERNAL_SERVER_ERROR.getCode(), errorInformation.get(0));
*/
return handleBaseBindAndMethodArgumentNotValidMessage(ex.getBindingResult().getAllErrors());
}
/**
* 处理校验数据
* @param allErrors 错误集
* @return 错误结果集
*/
protected Receipt handleBaseBindAndMethodArgumentNotValidMessage(List<ObjectError> allErrors) {
List<String> collect = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
return new Receipt(ExceptionCode.SYS_MEDIA_TYPE_EX.getCode(), collect.get(0));
}
}
import com.alibaba.fastjson.JSON;
import com.itllc.OpenFigenError.config.ExceptionHandlerAdvice;
import com.itllc.OpenFigenError.constants.ExceptionCode;
import com.itllc.OpenFigenError.exce.AccountBindException;
import com.itllc.OpenFigenError.exce.BaseUncheckedException;
import com.itllc.util.Result;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import feign.Logger.Level;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import java.io.InputStream;
/**
* 异常自动配置
*
**/
@Slf4j
@Configuration
@Import({ ExceptionHandlerAdvice.class })
public class ExecAutoConfiguration {
@Bean
public Level levl(){
return Level.FULL;
}
@Bean
public ErrorDecoder errorDecoder() {
return new DefaultErrorDecoder();
}
/**
* 重新实现feign的异常处理,捕捉restful接口返回的json格式的异常信息
*/
public static class DefaultErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
// 这一部分的异常将会变成子系统的异常, 不会进入hystrix的fallback方法,将会进入ErrorFilter的过滤链路
if (response.status() >= HttpStatus.BAD_REQUEST.value() && response.status() < HttpStatus.INTERNAL_SERVER_ERROR.value()) {
try {
InputStream is = response.body().asInputStream();
Result receipt = JSON.parseObject(IOUtils.toString(is), Result.class);
if (receipt.getCode() == ExceptionCode.ACCOUNT_BIND.getCode()){
return new AccountBindException(receipt.getMsg());
}
return BaseUncheckedException.getInstance(receipt);
} catch (Exception e) {
}
}
// 这一部分会进入fallback
return feign.FeignException.errorStatus(methodKey, response);
}
}
}
异常处理(熔断异常、正常的抛出异常)
未处理
根据上面的异常处理需要在zuul中配置正常的返回
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
/**
*
* 所有请求全是成功
*
* @author WangLin
* @since 2020-07-27 15:18:03
*
*/
@Component
public class SuccessfulFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
//filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
return FilterConstants.SEND_ERROR_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
// 是否执行该过滤器,此处为true,说明需要过滤
return true;
}
/**
* filter需要执行的具体操作
* <p>
* 例如:本filter实际执行的逻辑 是验证所有的访问请求中,是否包含安全信息auth
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
response.setStatus(200);
// RequestContext ctx = RequestContext.getCurrentContext();
// if (!StringUtils.equals(ctx.getRequest().getRequestURI(), "/oauth/current")){
// HttpServletResponse response = ctx.getResponse();
// response.setStatus(200);
// }
return null;
}
}