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

由于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啊。

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

  1. java如何自定义异常_Java如何实现自定义异常类

    这篇文章主要介绍了Java如何实现自定义异常类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 自定义异常类步骤 创建一个类继承异常父类Excepti ...

  2. 在 Java Web 项目中,Service 层和 Dao 层真的有必要每个类都加上接口吗

    作者 l 会点代码的大叔(CodeDaShu) 很多程序员在刚开始工作的时候,接触到的项目都是这样做的:项目的代码被分成 Controller.Service.Dao 层,一个接口对应一个实现类,然后 ...

  3. java web环境_java web

    HTTP Status 500 - Request processing failed; nested exception is java.lang.NullPointerException HTTP ...

  4. java 实现 web 客户端_Java web客户端和服务器端交互的原理

    Java web客户端和服务器端交互的原理 其实HTTP客户端和服务器端的交互原理很简单:即先是浏览器和服务器端建立Socket无状态连接,也就是短连接,然后通过IO流进行报文信息(这个报文是严格遵循 ...

  5. java web 教程_Java Web服务教程

    java web 教程 Welcome to the Java Web Services Tutorial. Here we will learn about web services, useful ...

  6. java web 分页_Java Web(十一) 分页功能的实现

    虽然现在有很多好用的框架,对分页进行支持,很简单的就把分页的效果做出来,但是如果自己手写是一个怎样的流程的?今天就来说说它,手动实现分页效果. --WH 一.分页的思路 首先我们得知道写分页代码时的思 ...

  7. java开发原则_java开发中,大家处理异常的原则是什么,是如何处理的?

    展开全部 最熟悉的陌生人:异常 异常的类e5a48de588b63231313335323631343130323136353331333361326365型Throwable - Exception ...

  8. web开发的java语言步骤_java web开发入门一(servlet和jsp)基于eclispe

    servlet 用java语言开发动态资源网站的技术,在doGet方法中拼接显示html,在doPost方法中提交数据.类似于.net的ashx技术. servlet生成的class文件存放在tomc ...

  9. java 分布式同步_Java Web分布式集群搭建(三)——Session同步

    对于一个业务系统的Tomcat集群来说,必须保证同一个用户访问到任一台服务器上都可以维持之前操作的身份.比如在服务器A进行了登陆,那么在服务器B中也要同步该用户已登录的状态,这里就用到了Session ...

最新文章

  1. 跟着老司机玩转Node自定义命令行
  2. 获取当前目录所有文件名 并且保存为1个00000.txt的文件文件bat
  3. 【转】JNI学习积累之一 ---- 常用函数大全
  4. 好玩的WPF第四弹:用Viewport2DVisual3D实现3D旋转效果
  5. 优先级队列,代码参考范例
  6. 7-3 银行家算法--综合 (50 分)(思路+详解+分析输入)宝宝们 加油
  7. Leetcode--130. 被围绕的区域(java)
  8. mocha.js 生成覆盖率报告
  9. 数据库类型少_DELETE与DROP 在数据库中的使用方法和区别
  10. bzoj3594 [Scoi2014]方伯伯的玉米田
  11. 图:广州南洋理工职业学院管理系“相聚南洋”朗诵(演讲)比赛尘埃落定
  12. 如何查看计算机端口状态
  13. Dfinity(ICP)介绍-1
  14. 华为交换机设置端口镜像捕获数据包
  15. 【白皮书】用于过程工业的PROFINET
  16. 搭建es+kabana
  17. 华三路由器qos 简单的接口限速
  18. nmap简单实用命令
  19. 利用ZigBee和线程快速构建网格连接的照明应用
  20. Kafka常用命令收录

热门文章

  1. Shell—grep、sed、awk
  2. 图像处理与图像识别笔记(六)图像增强3
  3. 图像拼接2 特征匹配
  4. Linux下安装Redis(三分钟搞定)
  5. Chrome 爬虫插件 Web Scraper
  6. 如果linux目录中没有srv,了解linux系统目录,proc,root,sbin,selinux,srv!
  7. FreeRTOS学习及移植笔记之二:在IAR和STM32F103VET上移植FreeRTOS
  8. 探索比特币源码3-熟悉RPC接口
  9. Go Concurrency Patterns: Context
  10. skywalking(4)