SpringBoot —— 统一异常处理

前言

在 Controller 里提供接口,通常需要捕捉异常,进行异常处理。最简单的方法使用try/catch进行异常捕捉。

当方法很多,每个都需要 try catch,代码会显得臃肿,写起来也比较麻烦。
这时就需要进行统一的异常处理。

1.使用方法

通过 Spring 的 AOP 特性就可以很方便的实现异常的统一处理:使用@ControllerAdvice、@RestControllerAdvice捕获运行时异常。

代码结构


image.png

新建异常枚举类

package com.local.dev.root.devroot.common.enums;

/**
 * 异常枚举类
 */
public enum ExceptionEnum {
    // 400
    BAD_REQUEST("400", "请求数据格式不正确!"),
    UNAUTHORIZED("401", "登录凭证过期!"),
    FORBIDDEN("403", "没有访问权限!"),
    NOT_FOUND("404", "请求的资源找不到!"),
    // 500
    INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
    SERVICE_UNAVAILABLE("503", "服务器正忙,请稍后再试!"),
    // 未知异常
    UNKNOWN("10000", "未知异常!"),
    // 自定义
    IS_NOT_NULL("10001","%s不能为空");

    /**
     * 错误码
     */
    private String code;

    /**
     * 错误描述
     */
    private String msg;

    ExceptionEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

自定义异常类

package com.local.dev.root.devroot.common.exception;

import com.local.dev.root.devroot.common.enums.ExceptionEnum;

/**
 * 自定义业务异常类
 */
public class BusinessException extends RuntimeException {

    private ExceptionEnum exceptionEnum;
    private String code;
    private String errorMsg;

    public BusinessException() {
        super();
    }

    public BusinessException(ExceptionEnum exceptionEnum) {
        super("{code:" + exceptionEnum.getCode() + ",errorMsg:" + exceptionEnum.getMsg() + "}");
        this.exceptionEnum = exceptionEnum;
        this.code = exceptionEnum.getCode();
        this.errorMsg = exceptionEnum.getMsg();
    }

    public BusinessException(String code, String errorMsg) {
        super("{code:" + code + ",errorMsg:" + errorMsg + "}");
        this.code = code;
        this.errorMsg = errorMsg;
    }

    public BusinessException(String code, String errorMsg, Object... args) {
        super("{code:" + code + ",errorMsg:" + String.format(errorMsg, args) + "}");
        this.code = code;
        this.errorMsg = String.format(errorMsg, args);
    }

    public ExceptionEnum getExceptionEnum() {
        return exceptionEnum;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

ExceptionHandlerConfig.java
@RestControllerAdvice,统一异常处理

package com.local.dev.root.devroot.common.config.exception;

import com.local.dev.root.devroot.common.enums.ExceptionEnum;
import com.local.dev.root.devroot.common.exception.BusinessException;
import com.local.dev.root.devroot.common.exception.ErrorPageException;
import com.local.dev.root.devroot.common.pojo.ApiResponse;
import com.local.dev.root.devroot.common.util.ErrorUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * RestControllerAdvice,统一异常处理
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig {

    /**
     * 业务异常处理
     *
     * @param e 业务异常
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public ApiResponse exceptionHandler(BusinessException e) {
        log.error(ErrorUtil.errorInfoToString(e));
        return ApiResponse.error(e.getCode(), e.getErrorMsg());
    }

    /**
     * 未知异常处理
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ApiResponse exceptionHandler(Exception e) {
        // 把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));
        return ApiResponse.error(ExceptionEnum.UNKNOWN.getCode(),
                ExceptionEnum.UNKNOWN.getMsg());
    }

    /**
     * 错误页面异常
     */
    @ExceptionHandler(value = ErrorPageException.class)
    @ResponseBody
    public ApiResponse exceptionHandler(ErrorPageException e) {
        log.error(ErrorUtil.errorInfoToString(e));
        return ApiResponse.error(e.getCode(), e.getErrorMsg());
    }

    /**
     * 空指针异常
     */
    @ExceptionHandler(value = NullPointerException.class)
    @ResponseBody
    public ApiResponse exceptionHandler(NullPointerException e) {
        log.error(ErrorUtil.errorInfoToString(e));
        return ApiResponse.error(ExceptionEnum.INTERNAL_SERVER_ERROR.getCode(),
                ExceptionEnum.INTERNAL_SERVER_ERROR.getMsg());
    }
}

测试类

package com.local.dev.root.devroot.controller;

import com.local.dev.root.devroot.common.pojo.ApiResponse;
import com.local.dev.root.devroot.service.dev.TestServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloWorld {

    @Autowired
    private TestServiceImpl testServiceImpl;
    @RequestMapping("world")
    public String HelloWorld() {
        return "Hello World!";
    }

    @RequestMapping("error1")
    public ApiResponse Error1() {
        return ApiResponse.ok();
    }
    @RequestMapping("error2")
    public ApiResponse Error2() {
        String msg = null;
        msg.equals("xx");
        return ApiResponse.ok("访问成功");
    }
    @RequestMapping("error3")
    public ApiResponse Error3() {
        testServiceImpl.getBusinessException();
        return ApiResponse.ok("访问成功");
    }
}

testServiceImpl 抛出自定义异常

package com.local.dev.root.devroot.service.dev;

import com.local.dev.root.devroot.common.enums.ExceptionEnum;
import com.local.dev.root.devroot.common.exception.BusinessException;
import org.springframework.stereotype.Service;

@Service
public class TestServiceImpl {

    public Object getBusinessException() {
        throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),
                ExceptionEnum.IS_NOT_NULL.getMsg(), "参数");
    }
}

ErrorUtil工具类

package com.local.springboot.springbootcommon.utils;

import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * 捕获报错日志处理工具类
 */
public class ErrorUtil {

    /**
     * Exception出错的栈信息转成字符串
     * 用于打印到日志中
     */
    public static String errorInfoToString(Throwable e) {
        //try-with-resource语法糖 处理机制
        try(StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)){
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
            return sw.toString();
        }catch (Exception ignored){
            throw new RuntimeException(ignored.getMessage(),ignored);
        }
    }
}

ApiResponse

package com.local.springboot.springbootcommon.reponse;

import java.util.HashMap;

/**
 * @author: MaoDeShu
 * @date: 2021-09-17 11:29
 * @Description: API请求返回类
 **/
public class ApiResponse extends HashMap<String, Object> {
    public ApiResponse() {
        put("code", 200);
        put("msg", "success");
    }

    public ApiResponse(String code, String msg) {
        super(2);
        put("code", code);
        put("msg", msg);
    }

    public static ApiResponse ok() {
        ApiResponse r = new ApiResponse();
        r.put("msg", "操作成功");
        return r;
    }

    public static ApiResponse msg(String msg) {
        ApiResponse r = new ApiResponse();
        r.put("msg", msg);
        return r;
    }

    public static ApiResponse ok(Object obj) {
        ApiResponse r = new ApiResponse();
        r.put("code", 200);
        r.put("results", obj);
        return r;
    }

    public static ApiResponse error() {
        return error("500", "未知异常,请联系管理员");
    }

    public static ApiResponse error(String msg) {
        return error("500", msg);
    }

    public static ApiResponse error(String code, String msg) {
        ApiResponse r = new ApiResponse();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    @Override
    public ApiResponse put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 分页
     *
     * @param page
     * @return
     */
    public ApiResponse page(Object page) {
        return put("page", page);
    }


    public ApiResponse result(Object obj) {
        super.put("result", obj);
        return this;
    }
}

2.测试

正常
http://localhost:8080/hello/error1

image.png

空指针异常
http://localhost:8080/hello/error2
image.png

业务异常
http://localhost:8080/hello/error3
image.png

3.查看日志

image.png

打开error日志


image.png

« 上一章:SpringBoot —— 多线程定时任务的实现(注解配置、task:annotation-driven配置)
» 下一章:SpringBoot —— 简单整合Redis实例及StringRedisTemplate与RedisTemplate对比和选择

创作不易,关注、点赞就是对作者最大的鼓励,欢迎在下方评论留言
求关注,定期分享Java知识,一起学习,共同成长。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容