java web 自定义异常_Java web, service 层应该通过异常(自定义Exception)来中断业务吗?...

同意!但是不是最佳实践还有待商榷,我这里给出自己一直使用的用异常控制流程的方案,分享讨论一下吧。

由于JAVA只能有一个返回值,但有时候一个service方法除了返回结果外还真的需要有一些附加信息,比如用户非法操作时要中断流程并给出错误信息。如果你不想在service方法中充满着各种大多时候都无用的ResultBean或者让人看见就头大的Map,异常的确值得去尝试一下。

异常最大的优势就是可以中断方法并返回附加信息,方便统一管理,使你的代码更简洁。

缺点就是性能,至于差多少还没测试过,希望有人可以去测一下。

下面开始主题内容:

1.定义异常;

我理解的业务异常是指用户非法操作(如注册用户名重复)需要中断操作并给用户返回合理信息的异常。定义如下:

先定义一个继承自RuntimeException的异常,主要在最后一个构造方法,后面两个值为false,false。意思就是不调用fillIStackTrace()方法和不添加suppressException;因为我们主要关注错误信息,并不在意栈轨迹,所以禁用他们用来提高性能。当然也可以通过复写特定方法来实现,我只是觉得用构造方法更简单。

public class UnFillStackTraceException extends RuntimeException {

private static final long serialVersionUID = -3181827538683088424L;

public UnFillStackTraceException() {

this(null, null);

}

public UnFillStackTraceException(String message) {

this(message, null);

}

public UnFillStackTraceException(Throwable cause) {

this(null, cause);

}

public UnFillStackTraceException(String message, Throwable cause) {

super(message, cause, false, false);

}

}

接下来定义业务异常:

public class APIException extends UnFillStackTraceException {

private static final long serialVersionUID = -1043498038361659805L;

private final StatusCode statusCode;

public APIException(StatusCode statusCode) {

this.statusCode = statusCode;

}

public APIException(StatusCode statusCode, String message) {

super(message);

this.statusCode = statusCode;

}

public StatusCode getStatusCode() {

return this.statusCode;

}

@Override

public String getMessage() {

return StringUtils.defaultIfBlank(super.getMessage(), statusCode.defaultMessage);

}

}

很简单,上面的看一下就明白。因为我们说了业务异常必须是明确,可以给用户提示的错误,所以要构造APIException必须设置相应的StatusCode。

2. StatusCode的设计

业务异常之中肯定要包含相应的错误信息,一般用代码来表示,代码设计的方式有好多种,这里我采用的方案是:基于HttpStatusCode的基础上扩展三位。好处就是可以和HTTP状态码相互转换,因为我们前台返回的时候都是基于http状态码的。

代码如下:

public enum StatusCode {

/**

* 服务器未知异常

*/

ERROR(500000, "服务器异常"),

//授权异常

DISABLE_ACCOUNT(401001, "账户已被冻结"),

INVALID_TOKEN(401002, "无效的身份凭证"),

EXPIRED_TOKEN(401003, "身份凭证已过期"),

NO_PERMISSION(401004, "无权限进行该操作"),

BAD_CREDENTIALS(401005, "密码错误"),

ILLEGAL_OPERATION(400001, "非法操作"),

NOT_FOUND(404000,"访问的资源不存在"),

INVALID_PARAM(422001, "参数无效");

public final int code;

public final String defaultMessage;

StatusCode(int code, String defaultMessage) {

this.code = code;

this.defaultMessage = defaultMessage;

}

public int getHttpStatusCode(){

return convertToHttpStatus(this);

}

public static StatusCode valueOf(int code) {

for (StatusCode value : StatusCode.values()) {

if (value.code == code) {

return value;

}

}

throw new IllegalArgumentException("没有符合'" + code + "'的StatusCode");

}

public static int convertToHttpStatus(StatusCode statusCode) {

return statusCode.code / 1000;

}

public static int convertToHttpStatus(int code) {

return convertToHttpStatus(valueOf(code));

}

}

3. 捕获异常

这一步基本上没啥说的,统一用ControllerAdvice捕获就行了。

@RestControllerAdvice

public class ControllerExceptionHandler {

public static final Logger log = LoggerFactory.getLogger(ControllerExceptionHandler.class);

@ExceptionHandler(APIException.class)

public ResponseEntity handleBusinessException(APIException apiException){

return ResponseEntity.status(apiException.getStatusCode().getHttpStatusCode())

.body(new ErrorBody(apiException));

}

@ExceptionHandler(Exception.class)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

public ErrorBody handleUnknownException(Exception e){

log.error("服务器未知异常",e);

return new ErrorBody(StatusCode.ERROR);

}

}

4. 使用

定义好这些东西后就可愉快的使用了,Service中简单的代码如下:

@Override

@Transactional(rollbackFor = Exception.class)

public SecurityAccount registerAccount(AccountRegister register) {

accountRepository.findUserByUsername(register.getUsername()).ifPresent(eac->{

throw new InvalidParamException("用户名:" + register.getUsername() + "已被使用");

});

Account account = BeanUtil.copyBean(register,Account.class);

if (StringUtils.isNotBlank(register.getPassword())){

account.setPassword(passwordEncoder.encode(register.getPassword()));

} else {

throw new InvalidParamException("密码不能为空");

}

account.setCreateTime(LocalDateTime.now());

accountRepository.save(account);

return convertToSecurity(account);

}

InvalidParamException是继承自ApiException并在构造函数中设置好状态码,方便使用。

Controller层代码:

Controller层没有对返回结果再做封装,因为大多时候根本没必要。尽量利用http状态码即可,对前端使用很舒服。

如果发生错误,再统一返回ErrorBody,里面有错误码和详细信息,供前端展示。对于有复杂业务的操作,如不能简单的使用成功或者失败来表示的,就自己再针对业务和前端协商专门定义即可。

@PostMapping

public long registerAccount(@Validated @RequestBody AccountRegister accountRegister,

BindingResult result) {

checkBindingResult(result);

return accountService.registerAccount(accountRegister).getId();

}

使用http状态码返回错误后,前端使用相当舒服,不用再为业务异常捕获一次,为http错误再捕获一次,统一进catch即可:

$http.post("system/accounts", this.editInfo).then(res => {

this.$message.success("操作成功");

this.cancelDialog();

this.loadData();

}).catch(reason => {

if (!reason.handled) {

this.$message.error(reason.response.data.message);

}

})

这些只是我个人习惯中总结下来的实践,并非最佳实践。放在这里供大家讨论一下,希望能多指出不足,一起学习改进。另外说一句,知乎的电脑端编辑器好难用,,好像有不少bug啊。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值