统一异常处理(Spring)

说明
本文只是自用的笔记,并不是大佬写的那种技术博文,如需要的小伙伴可参考思路。

1 问题

1、代码只要操作不成功仅向用户返回“错误信息”,无法区别具体的错误信
息。
2、service方法在执行过程出现异常在哪捕获?在service中需要都加try/catch,如果在controller也需要添加
try/catch,代码冗余严重且不易维护。
解决方案:
1、在Service方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成
功信息。
2、在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息。

2 异常处理流程

系统对异常的处理使用统一的异常处理流程:
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。
可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较
齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。
4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。
不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为
RuntimeException类型(运行时异常)。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随
请求响应给客户端。

异常抛出及处理流程:

java spring 崩溃收集_错误代码

1、在controller、service、dao中程序员抛出自定义异常;springMVC框架抛出框架异常类型

2、统一由异常捕获类捕获异常,并进行处理

3、捕获到自定义异常则直接取出错误代码及错误信息,响应给用户。

4、捕获到非自定义异常类型首先从Map中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误
信息并响应给用户,如果从Map中找不到异常类型所对应的错误代码则统一为99999错误代码并响应给用户。

5、将错误代码及错误信息以Json格式响应给用户。

3 可预知异常处理

示例:

3.1 准备工作

3.1.1 创建错误代码接口

创建ResultCode接口,所有错误信息类都继承此接口

package com.xxx.xxx.model.response;

public interface ResultCode {
    //操作是否成功,true为成功,false操作失败
    boolean success();
    //操作代码
    int code();
    //提示信息
    String message();
}

3.1.2 封装公共错误代码

创建公共错误代码枚举类CommonCode

在common工程定义异常类型,为其他的错误信息提供信息。

package com.xxx.xxx.model.response;

@ToString
public enum CommonCode implements ResultCode{

    SUCCESS(true,10000,"操作成功!"),
    FAIL(false,11111,"操作失败!"),
    UNAUTHENTICATED(false,10001,"此操作需要登陆系统!"),
    UNAUTHORISE(false,10002,"权限不足,无权操作!"),
    SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!");
    /**
    * ImmutableMap是Google的一个map,此map一旦赋值就不可再次赋值
    */
    private static ImmutableMap<Integer, CommonCode> codes ;
    //操作是否成功
    boolean success;
    //操作代码
    int code;
    //提示信息
    String message;
    private CommonCode(boolean success,int code, String message){
        this.success = success;
        this.code = code;
        this.message = message;
    }

    @Override
    public boolean success() {
        return success;
    }
    @Override
    public int code() {
        return code;
    }

    @Override
    public String message() {
        return message;
    }
}

3.1.3 创建响应结果接口

主要是向前台响应错误代码

package com.xxx.xxx.model.response;

public interface Response {
    public static final boolean SUCCESS = true;
    public static final int SUCCESS_CODE = 10000;
}

3.1.4 创建响应结果接口实现类

具体想响应的数据自己定义

package com.xxx.xxx.model.response;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
public class ResponseResult implements Response {

    //操作是否成功
    boolean success = SUCCESS;

    //操作代码
    int code = SUCCESS_CODE;

    //提示信息
    String message;

    public ResponseResult(ResultCode resultCode){
        this.success = resultCode.success();
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public static ResponseResult SUCCESS(){
        return new ResponseResult(CommonCode.SUCCESS);
    }
    public static ResponseResult FAIL(){
        return new ResponseResult(CommonCode.FAIL);
    }

}

3.2 创建自定义异常类

自定义异常用来排除项目可预知的异常

package com.xxx.xxx.exception;

import com.xxx.xxxx.model.response.ResultCode;

public class CustomException extends RuntimeException {
    private ResultCode resultCode;

    public CustomException(ResultCode resultCode) {
        super("错误代码:"+resultCode.code()+",错误信息:"+resultCode.message());
        this.resultCode = resultCode;
    }

    /**
     * 获得错误代码
     * @return
     */
    public ResultCode getResultCode() {
        return resultCode;
    }
}

3.3 创建自定义异常抛出类

此类主要是为了抛出异常方便

package com.xxx.xxx.exception;

import com.xxx.xxx.model.response.ResultCode;


public class ExceptionCast {
    public static void cast(ResultCode resultCode){
        throw new CustomException(resultCode);
    }
}

3.4 创建异常捕获类

捕获已知异常和未知异常

package com.xxx.xxx.exception;

import com.xxx.xxx.model.response.ResponseResult;
import com.xxx.xxx.model.response.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

//使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异
@ControllerAdvice
public class ExceptionCatch {
    //用来记录日志
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

    //捕获 CustomException异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody //加这个注解是为了向前台响应json数据
    public ResponseResult customException(CustomException e) {

        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);
        ResultCode resultCode = e.getResultCode();
        ResponseResult responseResult = new ResponseResult(resultCode);
        
        return responseResult;
    }
}

3.5 封装具体服务中的错误代码

这里是具体项目的错误代码(注意这里用了枚举类),在具体的微服务或者项目模块下定义

例:

package com.xxx.xxx.domain.cms.response;

import com.xxx.xxx.model.response.ResultCode;
import lombok.ToString;

@ToString
public enum CmsCode implements ResultCode {
    CMS_ADDPAGE_EXISTSNAME(false,24001,"页面名称已存在!"),
    CMS_GENERATEHTML_DATAURLISNULL(false,24002,"从页面信息中找不到获取数据的url!"),
    CMS_GENERATEHTML_DATAISNULL(false,24003,"根据页面的数据url获取不到数据!"),
    CMS_GENERATEHTML_TEMPLATEISNULL(false,24004,"页面模板为空!"),
    CMS_GENERATEHTML_HTMLISNULL(false,24005,"生成的静态html为空!"),
    CMS_GENERATEHTML_SAVEHTMLERROR(false,24005,"保存静态html出错!"),
    CMS_COURSE_PERVIEWISNULL(false,24007,"预览页面为空!");
    //操作代码
    boolean success;
    //操作代码
    int code;
    //提示信息
    String message;
    private CmsCode(boolean success, int code, String message){
        this.success = success;
        this.code = code;
        this.message = message;
    }

    @Override
    public boolean success() {
        return success;
    }

    @Override
    public int code() {
        return code;
    }

    @Override
    public String message() {
        return message;
    }
}

3.6 测试

访问一个请求(“localhost:11001/xxx/xx”)调用方法然后添加下面的代码

//抛出异常
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTSNAME);

得到结果

{
  "success": false,
  "code": 24001,
  "message": "页面名称已存在!"
}

4 不可预知异常处理

4.1 在异常捕获类(ExceptionCatch)中添加对Exception异常的捕获方法

@ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult exception(Exception exception) {
        //记录日志
        LOGGER.error("catch exception:{}", exception.getMessage());
        return null;
    }

4.2 异常捕获方法

package com.xxx.xxx.exception;

import com.google.common.collect.ImmutableMap;
import com.xxx.xxx.model.response.CommonCode;
import com.xxx.xxx.model.response.ResponseResult;
import com.xxx.xxx.model.response.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

//使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异
@ControllerAdvice
public class ExceptionCatch {
    //用来记录日志
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

    //使用EXCEPTIONS存放异常类型和错误代码的映射,ImmutableMap的特点的一旦创建不可改变,并且线程安全
    private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
    //使用builder来构建一个异常类型和错误代码的异常,以异常类作为key,异常代码作为value
    protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();

    static{
        //在这里加入一些基础的异常类型判断,如此处HttpMessageNotReadableException异常
        builder.put(HttpMessageNotReadableException.class,CommonCode.INVALIDPARAM);
    }

    //捕获未知异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult exception(Exception exception) {
        LOGGER.error("catch exception : {}\r\nexception: ",exception.getMessage(), exception);
        if(EXCEPTIONS == null)
            EXCEPTIONS = builder.build();
        final ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
        final ResponseResult responseResult;
        if (resultCode != null) {
            responseResult = new ResponseResult(resultCode);
        } else {
            responseResult = new ResponseResult(CommonCode.SERVER_ERROR);
        }

        //记录日志
        LOGGER.error("catch exception:{}", exception.getMessage());
        return responseResult;
    }
    
    //捕获 CustomException异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody //向前台响应json数据
    public ResponseResult customException(CustomException e) {

        LOGGER.error("catch exception : {}\r\nexception: ", e.getMessage(), e);
        ResultCode resultCode = e.getResultCode();
        ResponseResult responseResult = new ResponseResult(resultCode);
        return responseResult;
    }
}