最近看了几个新员工的代码,发现对异常运用有些不恰当,着重讨论一下自定义业务异常类在项目中的使用。
大家都知道JVM能处理的最大异常类是Exception,那么是不是异味着我们在项目中就可以直接捕获Exception呢?确实,可以这么做,但是不够具体,或者说不够友好。为什么这么说,因为在一个java项目中会出现异常的情况实在太多,如果只捕获Exception,那么就意味着我们所有异常都会被捕获。这么一来是不是我们想要的呢?
Java 中异常结构:
最常见场景之事务管理
springframework中Transactional注解可以说是最常见的事务管理方式之一,比如我们在下订单的Service层的saveOrder方法上加@Tractional注解指定Exception异常来回滚
@Transactional(rollbackFor = Exception.class)
public int saveOrder(Order order) {
业务代码。。。
}
这样写完全没问题,遇到异常事务肯定能回滚,那么我们有必要到指定到Exception级别吗?通过上面的异常结构可以看到,Exception下面分为:
unchecked exception(非检查异常):包括运行时异常(RuntimeException)和派生于Error类的异常。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
checked exception(检查异常,编译异常,必须要处理的异常)
也:称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。
这就说明我们正常业务代码中其实只需要关注RuntimeException,比如空指针、数组下标越界、类型转换等运行时异常,所以我们在上面下订单方法是改造一下
@Transactional(rollbackFor = RuntimeException.class)
public int saveOrder(Order order) {
业务代码。。。
}
接下来我们再看下Springframework 中 Trasactional中关于rollbackFor的源码:
写的很清楚,rollbackFor的default是RuntimeException和Error(unchecked exception),不包含checked exception,也就是说就异常而言,Trasactional默认的异常级别是RuntimeException。那么,我们上面下订单代码可以就可以不需要手动指定为RuntimeException了,改造如下:
@Transactional
public int saveOrder(Order order) {
业务代码。。。
}
自定义业务异常
如果在下订单的业务代码中添加判断库存不足的时候不允许下单,并通过事务混滚前面关于下单的相关持久化操作,第一想法是通过判断库存不足抛出RuntimeException(下面为虚造下单业务,存粹为了举例)
@Transactional
public int saveOrder(Order order) {
//业务代码1:
insertOrder(order);
//业务代码2:
if(stockNum==0){
throw new RuntimeException("下单失败,原因:库存不足");
}
}
在控制层进行捕获RuntimeException,并返回给前端异常信息:
@PostMapping("/saveOrder")
@ResponseBody
public Response saveOrder(@RequestBody Order order) {
Response response = new Response(”下单成功“);
try {
orderService.saveOrder(org);
} catch (RuntimeException e) {
e.printStackTrace();
return Response.fail(e.getMessage);
} catch (Exception e) {
e.printStackTrace();
return Response.fail();
}
return response;
}
这种写法前端很容易就能的得到异常信息,但是很明显前端不会再去细分后端返回的异常信息是不是业务异常。也就是说,我们库存不足是业务异常的范围,类似的异常信息很多,比如:余额不足,库存不足,活动过期等,几乎每个系统都会有很多业务异常信息。如果我们都通过捕获RuntimeException的信息返回给前端,那么我们的空指针、下标越界这些异常信息也会被前端获取,很显然这样不太合理,虽然事务管理起来了。
这样就要求我们去定义一个业务异常类,让这个业务异常类继承RuntimeException,用于抛出我们的业务异常信息:
/**
* @Description 业务异常包装类
* @Date 2020/5/17 15:10:00
* @Created by 海哥
*/
public class BusinessException extends RuntimeException{
private static final long serialVersionUID = 2332608236621015980L;
//业务异常编码
private String code;
//业务异常信息(前端可见的)
private String message;
public BusinessException() {
super();
}
public BusinessException(String message) {
super(message);
this.message = message;
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
这样定义了一个业务异常类BusinessException,并继承RuntimeException,这样我们上面Service层中判断库存的下单方法可以改造为:
@Transactional
public int saveOrder(Order order) {
//业务代码1:
insertOrder(order);
//业务代码2:
if(stockNum==0){
throw new BusinessException("下单失败,原因:库存不足");
}
}
控制层接口则改用捕获BusinessException的方式,改造如下:
@PostMapping("/saveOrder")
@ResponseBody
public Response saveOrder(@RequestBody Order order) {
Response response = new Response(”下单成功“);
try {
orderService.saveOrder(org);
} catch (BusinessException e) {
e.printStackTrace();
return Response.fail(e.getMessage);
} catch (Exception e) {
e.printStackTrace();
return Response.fail();
}
return response;
}
这样改造之后,我们只需要在控制层关注BusinessException,作为所有业务异常通过BusinessException统一处理,其他的异常均为非业务异常,用Exception捕获即可。
由于没有上传GitHub,附上返回给前端的Response类:
public class Response{
private Object data;
private Page page;
//默认为0表示响应正常
private int code = 0;
private String msg;
public Response() {
}
public Response(Object data) {
this.data = data;
}
public Response(Object data, Page page) {
this.data = data;
this.page = page;
}
public static Response ok(){
return Response.ok("操作成功");
}
public static Response applyOk(){
return Response.ok("提交成功");
}
public static Response ok(String msg){
Response response = new Response(msg);
return response;
}
public static Response ok(String msg, Object data){
Response response = new Response(msg);
response.setData(data);
return response;
}
public static Response fail(){
return Response.fail("操作失败");
}
public static Response fail(String msg){
Response response = new Response(msg);
response.setCode(Constants.FAIL_CODE);
return response;
}
public static Response fail(String msg, Object data){
Response response = fail(msg);
response.setData(data);
return response;
}
public Response(String msg) {
this.msg = msg;
}
public Response(int code,String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return this.data;
}
public void setData(Object data) {
this.data = data;
}
public Page getPage() {
return page;
}
public void setPage(Page page) {
this.page = page;
}
}