0001-Java项目中统一返回统一异常处理.md

先创建一个crud的项目。

controller调用service调用mapper

以下以简单代码代替

  • controller
    @GetMapping("/getUserById")
    public String getUserById(String id){
        String userById = userService.getUserById(id);
        return userById;
    }
  • service
    @Override
    public String getUserById(String id) {
        // 模拟业务
        User user = userMapper.selectById(id);
        return user.toString();
    }

上面代码虽然是把数据返回给前台了,但是没有处理异常以及没有一个明确的标识告诉前端我是成功还是失败了,此时我们需要封装一下统一的成功失败标志来告诉前台如何处理返回数据。(lombok的使用要综合考虑,不要因为编码一时爽引入导致后期Java版本升级困难)

使用Lombok 简单封装了一个CommonResponse类

@Data
public class CommonResponse {
    /**
     * 返回业务码用来判断成功失败
     * 200 成功
     * 500 失败
     */
    private String code;

    /** 描述 */
    private String massage;

    /** 描述 */
    private Object date;

    public CommonResponse(String code, String massage, Object date) {
        this.code = code;
        this.massage = massage;
        this.date = date;
    }

    public static CommonResponse succeed(){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), null);
    }
    public static CommonResponse succeed(Object date){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), date);
    }
    public static CommonResponse succeed(String massage,Object date){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), massage, date);
    }

    public static CommonResponse error(String massage){
        return getCommonResponse(CodeEnum.ERROR.getCode(), massage, null);
    }
    public static CommonResponse error(String code,String massage){
        return getCommonResponse(code, massage, null);
    }
    public static CommonResponse error(){
        return getCommonResponse(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMassage(), null);
    }

    public static CommonResponse getCommonResponse(String code, String massage, Object date){
        return new CommonResponse(code,massage,date);
    }
}

返回的controller使用统一的CommonResponse

    @GetMapping("/getUserById")
    public CommonResponse getUserById(String id){
        String userById = userService.getUserById(id);
        return CommonResponse.succeed(userById);
    }

返回

{
    "code": "200",
    "massage": "成功",
    "date": "User(id=1, username=嘉文00, password=1000000, age=5555)"
}

上述返回基本符合预期,但是当程序出现未知异常怎么办了。
对service改造下

    @Override
    public String getUserByIdException(String id) {
        User user = userMapper.selectById(id);
        // 模拟业务异常
        int i=5/0;
        return user.toString();
    }

controller 改造

@GetMapping("/getUserById")
    public CommonResponse getUserById(String id){
        try{
            String userById = userService.getUserById(id);
            return CommonResponse.succeed(userById);
        }catch(Exception e){
            e.printStackTrace();
            log.error(e.getMessage());
            return CommonResponse.error(e.getMessage());
        }
    }

上面是不是也可以,通过trycatch来判断如何返回。但是这样代码中就会出现大量的try catch,是不是很不好看,我们能不能添加一个统一的try呢,答案是可以的。

使用spring提供的统一的异常处理
spring 提供了三种异常捕获方式,个人比较推荐这一种

@Slf4j
@ControllerAdvice
public class ExceptionHandle {
    /**
     * 处理未知异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public CommonResponse handleException(Exception e){
        log.error("系统异常:{}",e.getMessage());
        return CommonResponse.error(e.getMessage());
    }

    /**
     * 处理主动抛出的自定义异常
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public CommonResponse handleBusinessException(BusinessException e){
 
        log.error("自定义异常:{}",e.getErrMassage());
        return CommonResponse.error(e.getErrCode(),e.getErrMassage());
    }
}

踩过的坑

  1. 这个地方在写的时候遇到一个坑,因为捕获异常后的返回值是CommonResponse,所以要加上注解 @ResponseBody 便于 格式转换。
  2. 在配置@ExceptionHandler的时候不能配置两个相同的Exception。否则会不知道使用哪个而报错。

这时controller 已经很清晰了,如下:只用处理好业务调用,无需处理向上抛出的异常。

    @GetMapping("/getUserByIdException")    public CommonResponse getUserByIdException(String id){        String userById = userService.getUserByIdException(id);        return CommonResponse.succeed(userById);    }    @GetMapping("/getUserByIdBusinessException")    public CommonResponse getUserByIdBusinessException(String id){        String userById = userService.getUserByIdBusinessException(id);        return CommonResponse.succeed(userById);    }

当然有时候我们会遇到自己的校验不通过来终止程序。我们可以throw 一个Exception 或者我们需要定制返回码,自定义一个异常类也行。如下简单示例,大家可以根据自己的业务需求去自定义。

  • 自定义异常类BusinessException
@Datapublic class BusinessException extends RuntimeException{    private static final long serialVersionUID = 918204099850898995L;    private String errCode;    private String errMassage;    public BusinessException(String errCode,String errMassage){        super(errMassage);        this.errCode = errCode;        this.errMassage = errMassage;    }}
  • service返回自定义异常
    @Override    public String getUserByIdBusinessException(String id) {        User user = userMapper.selectById(id);        // 模拟业务异常        if("1".equals(id)){            throw new BusinessException("400","id为1的数据不支持查询。");        }        return user.toString();    }

此时我们前端得到的返回

## 请求http://localhost:8088/getUserByIdBusinessException?id=1## 返回{    "code": "400",    "massage": "id为1的数据不支持查询。",    "date": null}

以上就是统一异常捕获跟统一的返回。

另外我们实际项目为了使业务异常好看,统一,我们可以定义一个枚举来存放我们的业务异常信息。

  1. 定义一个枚举类
public enum ExceptionEnum {    BUSINESS_NOT_ONE("400","id为1的数据不支持查询"),    ERR_NOT_LOOK("401","长得太帅不让看")    // 往后面累加...    ;    private String code;    private String desc;    ExceptionEnum(String code, String desc) {        this.code = code;        this.desc = desc;    }    public void ThrowException(){        ThrowException(code,desc);    }    public void ThrowException(String errMassage){        errMassage = desc +":"+errMassage;        ThrowException(code,errMassage);    }    private BusinessException ThrowException(String code,String desc){        throw new BusinessException(code,desc);    }}
  1. 在service 中抛出枚举异常
    @Override    public String getUserByIdBusinessExceptionByEnumOne(String id) {        User user = userMapper.selectById(id);        // 模拟业务异常        if("1".equals(id)){            ExceptionEnum.BUSINESS_NOT_ONE.ThrowException();        }        return user.toString();    }    @Override    public String getUserByIdBusinessExceptionByEnumTwo(String id) {        User user = userMapper.selectById(id);        // 模拟业务异常        if("look".equals(id)){            // 可以动态拼接异常信息            ExceptionEnum.ERR_NOT_LOOK.ThrowException("你说对吧"+id);        }        return user.toString();    }
  1. 前台返回
{    "code": "400",    "massage": "id为1的数据不支持查询",    "date": null}{    "code": "401",    "massage": "长得太帅不让看:你说对吧look",    "date": null}

这种做法的好处就是方便管理,一眼就知道自己项目中有多少错误,多少异常。但是也会有同学觉得这样写好费劲,每次抛异常的时候还要先在枚举类中定义。所以这个做法是项目跟成员而定。

源码地址

唯有面对风雨,才能面对未来和时光

posted @ 2021-09-17 23:22  神经哇  阅读(602)  评论(0编辑  收藏  举报