点击关注公众号,Java干货及时送达

1前言

在分布式服务的场景下,业务服务都将进行拆分,不同服务之前都会相互调用,如何做好异常处理是比较关键的,可以让业务人员在页面使用系统报错后,很清楚的看到服务报错的原因,而不是返回代码级别的异常报错,比如NullException、IllegalArgumentException、FeignExecption等异常报错,这样就会让非技术人员看到了一头雾水,从而很降低用户的体验感。

2服务调用异常场景

这是一个很常规的服务链路调用异常,前端用户请求A服务,A服务再去请求B服务,B服务出现了异常,A服务返回的Fallback降级的报错异常,但是显然这个异常并不是很能让人理解。
这是feign服务之前调用异常的报错,通过FeignException内部的异常处理类进行处理。

3重写Feign异常处理

首先我们可以通过实现feign的ErrorDecoder接口重写它的的decode方法,进行自定义异常处理,针对每个feign接口的异常报错,抛出自定义的exception将错误信息和错误码返回。

FeignExceptionConfiguration 自定义异常处理类

@Slf
4j

@Configuration
publicclassFeignExceptionConfiguration
{

@Bean
public ErrorDecoder errorDecoder()
{

returnnew
 UserErrorDecoder();

    }

/**

     * 重新实现feign的异常处理,捕捉restful接口返回的json格式的异常信息

     *

     */

publicclassUserErrorDecoderimplementsErrorDecoder
{


@Override
public Exception decode(String methodKey, Response response)
{

            Exception exception = 
new
 MyException();

            ObjectMapper mapper = 
new
 ObjectMapper();

//空属性处理
            mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY);

//设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
            mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, 
false
);

//禁止使用int代表enum的order来反序列化enum
            mapper.configure(DeserializationConfig.Feature.FAIL_ON_NUMBERS_FOR_ENUMS, 
true
);

try
 {

                String json = Util.toString(response.body().asReader());

                log.info(
"异常返回结果:"
+ JSON.toJSONString(json));

                exception = 
new
 RuntimeException(json);

if
 (StringUtils.isEmpty(json)) {

returnnull
;

                }

                FeignFaildResult result = mapper.readValue(json, FeignFaildResult
.class)
;

// 业务异常包装成自定义异常类MyException
if
 (result.getCode() != 
200
) {

                    exception = 
new
 MyException(result.getMsg(),result.getCode());

                }

            } 
catch
 (IOException ex) {

                log.error(ex.getMessage(), ex);

            }

return
 exception;

        }

    }

}

这里可以看到,经过处理后的异常返回结果,已经过滤掉feign的一长串异常,只留下code、msg、data等信息,直接映射到结果集对象上,通过自定义异常返回。

FeignFaildResult 异常结果集返回

/**

 *  根据 json 来定义需要的字段

 */

@Data
publicclassFeignFaildResult
{

private
 String msg;

privateint
 code;

}

MyException自定义异常

import
 lombok.Data;


@Data
publicclassMyExceptionextendsRuntimeException
{

// 自定义异常代码
privateint
 status = 
503
;


publicMyException()
{


    }


// 构造方法
publicMyException(String message, int status)
{

super
(message);

this
.status = status;

    }

}

FeignClient接口定义

@FeignClient
(contextId = 
"iTestServiceClient"
,

        value = 
"Lxlxxx-system2"
,

        fallback = TestServiceFallbackFactory
.
class
,

configuration
= FeignExceptionConfiguration
.
class
)

publicinterfaceITestServiceClient
{



/**

     * 服务调用测试方法

     * 
@return
     */

@GetMapping
(
"/test/method"
)

public R<String> testRequestMethod()throws Exception
;


}

通过@FeignClient注解里面的configuration属性,开启自定义异常处理。

被调用方服务

被调用方服务业务处理直接抛出异常即可

调用结果

代码中throw的异常message,直接可以返回给前端调用接口,这样报错信息也比较清楚。

4Spirng全局异常处理

当然也可以通过全局异常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,Spring 3.2也提供了相应的的注解类@ControllerAdvice,配合@ExceptionHandler注解,即可实现全局异常处理。

ResultCode异常错误码定义

首先先定义异常错误码枚举
publicenum
 ResultCode {

/*

     * 通用错误码 Code约定

     * 0表示成功[SUCCESS],                        看到0,业务处理成功。

     * 10000 - 19999表示业务警告[WARN_],           这种code不是常规武器,能免则免。

     * 20000 - 29999表示通用错误代码[ERR_],        各个系统通用的错误代码。

     * 30000 - 39999表示业务自定义错误代码[DIY_]

     * 40000 - 49999表示系统错误[SYS_],            系统错误单独拉出来,作为独立区域。理论上这部分也是通用的,不可以自定义。

     */

    SUCCESS(
"0"
"操作成功"
),

    ERR_LACK_PARAM(
"20001"
"请求参数不正确"
),

    ERR_NO_LOGIN(
"20002"
"用户未登录"
),

    ERR_NO_RIGHT(
"20003"
"没有权限访问该资源"
),

    ERR_NO_SERVICE(
"20004"
"资源不存在"
),

    ERR_WRONG_STATUS(
"20005"
"资源的当前状态不支持该操作"
),

    ERR_LACK_CONFIG(
"20006"
"缺少必要的配置项"
),

    ERR_PROCESS_FAIL(
"20007"
"业务处理失败"
),

    ERR_THIRD_API_FAIL(
"20008"
"调用第三方接口失败"
),

    ERR_IS_DELETED(
"20009"
"资源已删除"
),

    ERR_UPDATE_FAIL(
"20010"
"更新操作失败"
),

    SYS_MAINTENANCE(
"40001"
"系统维护中"
),

    SYS_BUSY(
"40002"
"系统繁忙"
),

    SYS_EXCEPTION(
"40003"
"系统异常"
);


private
 String code;

private
 String msg;


privateResultCode(String code, String msg)
{

this
.code = code;

this
.msg = msg;

    }


public String getCode()
{

returnthis
.code;

    }


publicvoidsetCode(String code)
{

this
.code = code;

    }


public String getMsg()
{

returnthis
.msg;

    }


publicvoidsetMsg(String msg)
{

this
.msg = msg;

    }


publicstatic ResultCode get(String code)
{

        ResultCode[] var1 = values();

int
 var2 = var1.length;


for
 (
int
 var3 = 
0
; var3 < var2; ++var3) {

            ResultCode statusEnum = var1[var3];

if
 (statusEnum.getCode().equals(code)) {

return
 statusEnum;

            }

        }

returnnull
;

    }


public String getErrorMsg(Object... params)
{

        String errorMsg = 
null
;

if
 (params != 
null
 && params.length != 
0
) {

            MessageFormat msgFmt = 
new
 MessageFormat(
this
.msg);

            errorMsg = msgFmt.format(params);

        } 
else
 {

            errorMsg = 
this
.msg;

        }


return
 errorMsg;

    }


}

BaseResult统一返回结果对象

@Data
publicclassBaseResult<TimplementsSerializable
{


privatestaticfinallong
 serialVersionUID = 
621986096326899992L
;


private
 String message;


private
 String errorCode;


private
 T data;


publicBaseResult()
{

    }


publicBaseResult(String message, String errorCode)
{

this
.message = message;

this
.errorCode = errorCode;

    }


publicstatic
 <T> 
BaseResult<T> success()
{

        BaseResult<T> baseResult = 
new
 BaseResult<>();

        baseResult.setMessage(ResultCode.SUCCESS.getMsg());

        baseResult.setErrorCode(ResultCode.SUCCESS.getCode());

return
 baseResult;

    }


publicstatic
 <T> 
BaseResult<T> success(T result)
{

        BaseResult<T> baseResult = 
new
 BaseResult<>();

        baseResult.setData(result);

        baseResult.setMessage(ResultCode.SUCCESS.getMsg());

        baseResult.setErrorCode(ResultCode.SUCCESS.getCode());

return
 baseResult;

    }


publicstatic
 <T> 
BaseResult<T> fail(ResultCode error)
{

        BaseResult<T> baseResult = 
new
 BaseResult<>();

        baseResult.setErrorCode(error.getCode());

        baseResult.setMessage(error.getMsg());

return
 baseResult;

    }

publicstatic
 <T> 
BaseResult<T> error(ResultCode error,String message)
{

        BaseResult<T> baseResult = 
new
 BaseResult<>();

        baseResult.setErrorCode(error.getCode());

        baseResult.setMessage(message);

return
 baseResult;

    }


publicstatic
 <T> 
BaseResult<T> fail(ResultCode error, Exception e)
{

        BaseResult<T> baseResult = 
new
 BaseResult<>();

        baseResult.setErrorCode(error.getCode());

        baseResult.setMessage(e.getMessage());

return
 baseResult;

    }


public Boolean isSuccess()
{

return"0"
.equals(
this
.errorCode) ? 
true
 : 
false
;

    }

}

CommonException自定义全局异常处理类

publicclassCommonExceptionextendsRuntimeException
{


private
 String code;


/**

     * 自己临时自定义状态码和状态信息

     *

     * 
@param
 code  状态码

     * 
@param
 message 状态信息

     */

publicCommonException(String code, String message)
{

super
(message);

this
.code = code;

    }


/**

     * 
@param
 resultCode 从枚举对象中获取状态码和状态信息

     */

publicCommonException(ResultCode resultCode)
{

super
(resultCode.getMsg());

this
.code = resultCode.getCode();

    }


}

ExceptionController全局异常处理控制类

@ControllerAdvice
publicclassExceptionController
{


/**

     * CommonException

     * 
@param
 e

     * 
@return
     */

@ExceptionHandler
(CommonException
.
class
)

    @
ResponseBody
publicBaseResulthandlerException
(
CommonExceptione
)
{

//异常返回false,Result是上一篇接口返回对象。
returnnew
 BaseResult(e.getMessage(),e.getCode());

    }


}

5调用结果

@RestController
@Slf
4j

publicclassTestController
{


@Autowired
private
 ITestServiceClient iTestServiceClient;



@GetMapping
(
"/testMethod"
)

public BaseResult testMethod()throws Exception 
{

try
 {

            log.info(
"通过feign调用system2服务~~~~~~~~~"
);

            R<String> stringR = iTestServiceClient.testRequestMethod();

        } 
catch
 (Exception e) {

thrownew
 CommonException(ResultCode.SYS_EXCEPTION.getCode(), ResultCode.SYS_EXCEPTION.getMsg());

        }


return
 BaseResult.success();


}

我还是模拟上面的场景,A服务去调B服务,B服务中抛出异常,通过定义的ExceptionController进行捕获异常,并且根据自定义的异常,拿到异常code和message进行返回,也是一种不错的选择。

6总结

以上两种服务之间调用异常处理的方法,分别在不同服务角度进行捕获处理,关于服务的异常处理,具体还要根据业务需求来进行处理,不同的异常可以分类进行捕获,例如基础异常、参数校验异常、工具类异常、业务检查异常等,都可以分开来进行定义,属于处理异常的一个规范定义。
来源:juejin.cn/post/7241395887943303226
--完--

ChatGPT 4.0 账号不用特殊网络了 !!!
目前GPT-4.0的功能:支持GPTs、联网功能、插件功能、上传文件、数据分析、AI画图、上传图片自动识别功能等 。这些功能都是3.5不能具备的 !
不过,这个功能目前只有升级Plus会员,才能使用 。
大家也都知道,官方开通的Plus会员,一个月20美元,相当于人民币180元每月,而且经常有被封号的风险 。
所以,这边一次性买了50多个Plus会员放在一个系统的池子里,共享给大家使用 。每月只需要90元,就可以直接使用 GPT 4.0 ,而且国内网络就可以直接登录 ,不需要额外的上网工具 。
复制购买链接到浏览器打开:https://qeosq.xetlk.com/s/408XZG
购买这个账号,一直有售后,不用担心中途封号或者用不了
或者直接微信付款后,加我微信:itcodexy,备注:90元购买plus账号
我会立马通过微信好友请求 。
扫码可以直接购买
继续阅读
阅读原文