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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/506577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 数据字典 spring_springboot+redis+切面实现数据字典功能

自定义注解:DataDict,用于bo对象类,需要翻译的属性package com.zddts.common.annotation.dict;import java.lang.annotation.*;/*** 说明:数据字典处理类* Created by luojie on 2019/05/29.*///DataDict( dict"patType"…

java支持多线程吗_Java多线程之一

进程与线程进程进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位,比如我们windows电脑上运行的一个程序就是一个进程。在传统进程中进程是资源分配和调度的一个基本单位,在后来引入线程概念后,进程就变成了资源分…

java继承与多态性_Java继承与多态浅析

一、继承1、通过extends继承的父类可以是不加abstract关键字的普通类,也可以是加了abstract关键字的抽象类。继承普通类时可以覆写父类的方法,或者创建自己独有的方法,或者这两 者都不使用。继承抽象类时,必须覆写抽象类中的…

java队列加锁_java并发-----浅析ReentrantLock加锁,解锁过程,公平锁非公平锁,AQS入门,CLH同步队列...

前言为什么需要去了解AQS,AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步…

java resttemplate_java-通过resttemplate通过Spring Rest服务发送文...

标题可能看起来很普通,但是没有一个适合我的问题.我有一个REST服务,它接受多部分形式的常规参数和文件.我想使用resttemplate将数据和文件发送到上述rest服务.直到我发送正常的字符串数据为止,没有任何问题.一旦我添加了发送字节的代码,那么我开始收到400错误的请求错误.如果我…

java中string的方法_java中String的常用方法

package com.string;public class string1 {public static void main(String args[]){//将char[]数组转换成Stringchar[] ch{h,我,是,中,国,人};String strnew String(ch);System.out.println(str); //结果为str"我是中国人"//将字符串转换为char数组,方法一使用getC…

Java是否为回文_java语言判断一个数字是否为回文数字

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。示例 1:输入: 121输出: true示例 2:输入: -121输出: false解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。示例 3:输入: 10输出: false解释: 从右向左读, 为…

java floyd_百度百科里面的floyd算法java的代码,总是无法运行。请问是代码有问题吗,如何编译啊?...

展开全部不能编译运行的说法是错误,但是结果是否正确,我就32313133353236313431303231363533e59b9ee7ad9431333335303539不知道了,我不懂这个算法public class FLOYD {int[][] length null;// 任意两点之间路径长度int[][][] path null;// …

cache数据库和mysql_并发环境下,先操作数据库还是先操作缓存?

原标题:并发环境下,先操作数据库还是先操作缓存?来源:捡田螺的小男孩前言在分布式系统中,缓存和数据库同时存在时,如果有写操作,先操作数据库还是先操作缓存呢?本文将分5种方案 展开…

contab 手动可以 java_crontab 定时执行脚本出错,但手动执行脚本正常

原因: crontab 没有去读环境变量,需要再脚本中手动引入环境变量,可以用source 也可以用export 写死环境变量。为了定时监控Linux系统CPU、内存、负载的使用情况,写了个Shell脚本,当达到一定值得时候,发送邮…

java的mybatis批量更新_mybatis批量更新的问题

一、问题描述场景描述:有这样一个service方法,调用了两个dao中的方法。第一个方法按照传入的id批量更新用户名。第二个dao方法无数据库操作,仅仅抛出一个RuntimeException.这个service方法通过xml配置由spring事务管理的。两个DAO类中分别有S…

java 查看垃圾收集器_JVM系列:查看JVM使用的什么垃圾收集器

一、方法一打印虚拟机所有参数[rootlocalhost ~]# java -XX:PrintFlagsFinal -version | grep :uintx InitialHeapSize : 258689024 {product}uintx MaxHeapSize : 4139778048 {product}bool PrintFlagsFinal : true {product}bool UseCompressedOops : true {lp64_product}boo…

java在W n8安装_在windows中安装JDK8并配置环境变量-java环境变量设置

学习JAVA,必须得安装一下JDK(Java development kit java开发工具包),配置一下环境就可以学习JAVA了,下面是下载和安装JDK的教程:一、去oracle官网上下载jdk8的下载地址:https://www.oracle.com/technetwork/java/javas…

lisp java_从Java调用的LISP代码

长篇小说:我正在为我的函数编程类做一个项目,我想到在Lisp中为Mario AI competition.我正在研究从Java调用LISP代码的框架/库/方式,甚至更好的LISP Java互通信。我看过Jacol但它是旧的,对我来说也不是很好。到目前为止,我的最佳选择是:Jatha.它真的很整洁,虽然一些Lisp构造还没…

java将图片上传数据库_〔技巧实例〕轻松实现将上传图片到数据库

很久就想自己写一写程序了,不过由于赖就不想写我,今天刚好有空,所以写了这个小小的程序很容易一看就知道的,不多说了就此开始:我们做一个上传的。数据据库的字段就id自动编号 big 字段类型是 OLE 呵呵就简单的那个字段…

mysql带参数的sql_MySql存储过程是带参数的存储过程(动态执行SQL语句)

下文介绍的MySql存储过程是带参数的存储过程(动态执行SQL语句)&#xff0c;该MySql存储过程是根据用户输入的条件和排序方式查询用户的信息&#xff0c;排序条件可以没有调用方式&#xff1a;call GetUsersDynamic(age<30,);/********动态查询用户的信息********/CREATE PRO…

java 注释 depredated_depredated是什么意思_depredated怎么读_depredated翻译_用法_发音_词组_同反义词-新东方在线英语词典...

双语例句1.Theentireareahasbeendepredatedinthewar.整个地区在战争中都遭到破坏。2.WehopethatHaitiwhich washeavilydepredatedby the killerquakewillbereconstructedintheforseeablefuture.我们希望在大地震中受重创的海地在不久的将来可以重建。3.TwoIssuesonAddresseesDe…

java计算雷达扫描范围_雷达扫描 - linyinmobayu - 博客园

1、设计思想雷达扫描图&#xff0c;在影视作品中见到较多&#xff0c;比如飞机雷达、舰艇雷达&#xff0c;有一个扫描线转圈代表雷达一周旋转或一个批次的收发&#xff0c;发现目标就在表盘上标记位置。和汽车仪表盘类似&#xff0c;汽车仪表盘有底盘背景图、同圆、刻度、刻度值…

mysql降序后去重_Mysql 数据记录去重后按字段排序

实现效果&#xff1a;去重—取最新的—排序例子 : 按用户ID获取历史记录中某个人的记录&#xff0c;要求非重复的且每条只获取最新的&#xff0c;同时按添加时间倒序排列的实现 &#xff1a;SELECT *FROM (SELECT *FROM historysWHERE types_id1ORDER BY created DESC) AS BGRO…

linux php 守护进程,PHP程序员玩转Linux系列 使用supervisor实现守护进程

PHP程序员玩转Linux系列文章&#xff1a;首先遇到的问题是,部署nodejs的博客程序时,我把执行nodejs的命令放到后台,使用加&和nohup命令如:nodejs index.js & 或者 nohup nodejs index.js &&这个使用是当退出此次终端会话的时候就会停止, nohup这个命令理论上是…