项目实战之统一处理自定义业务异常

最近看了几个新员工的代码,发现对异常运用有些不恰当,着重讨论一下自定义业务异常类在项目中的使用。

大家都知道JVM能处理的最大异常类是Exception,那么是不是异味着我们在项目中就可以直接捕获Exception呢?确实,可以这么做,但是不够具体,或者说不够友好。为什么这么说,因为在一个java项目中会出现异常的情况实在太多,如果只捕获Exception,那么就意味着我们所有异常都会被捕获。这么一来是不是我们想要的呢?

Java 中异常结构:

项目实战之统一处理自定义业务异常_第1张图片

最常见场景之事务管理

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的源码:

项目实战之统一处理自定义业务异常_第2张图片

写的很清楚,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;
    }

}

 

 

你可能感兴趣的:(一路向前,采坑记录)