Spring Boot异常处理

一、前言

Spring Boot对异常的处理有一套默认的机制:当应用中产生异常时,Spring Boot根据发送请求头中的accept是否包含text/html来分别返回不同的响应信息。当从浏览器地址栏中访问应用接口时,请求头中的accept便会包含text/html信息,产生异常时,Spring Boot通过org.springframework.web.servlet.ModelAndView对象来装载异常信息,并以HTML的格式返回;而当从客户端访问应用接口产生异常时(客户端访问时,请求头中的accept不包含text/html),Spring Boot则以JSON的格式返回异常信息。下面来验证一下。

二、默认异常处理机制

假设应用中有如下一个Controller:

java 复制代码
@RestController
@RequestMapping("calculate")
public class CalculateController {
    @GetMapping("/{divisor:\\d+}/{dividend:\\d+}")
    public void calculate(@PathVariable Long divisor,@PathVariable Long dividend) {
        throw new RuntimeException("calculate is error");
    }
}

在代码中我们主动的抛出了一个RuntimeException,使用浏览器访问http://localhost:8080/calculate/1/0

可看到页面返回了一些异常描述,并且请求头的accpet包含了text/html片段。

接着使用模拟发送REST请求,使用IDEA插件HTTP Client发送http://localhost:8080/calculate/1/0

可以看到请求头的accept值为*/*,并且返回一段JSON格式的信息。

查看Spring Boot的BasicErrorController类便可看到这一默认机制的具体实现:

可看到errorHtml和error方法的请求地址和方法是一样的,唯一的区别就是errorHtml通过produces = {"text/html"}判断请求头的accpet属性中是否包含text/html,如果包含,便走该方法。

三、自定义html异常页面

我们可以通过在src/main/resources/resources/error路径下定义友好的异常页面,比如定义一个500.html页面:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>500</title>
</head>
<body>
系统内部异常
</body>
</html>

使用浏览器再次访问http://localhost:8080/calculate/1/0

同样的,我们也可以定义404.html等常见的HTTP状态码对应的异常页面。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404</title>
</head>
<body>
链接走丢了
</body>
</html>

使用浏览器访问http://localhost:8080/calculater/1/0

通过自定义html异常页面并不会影响客户端发送请求异常返回的结果。

四、自定义异常处理

除了可以通过自定义html异常页面来改变浏览器访问接口时产生的异常信息,我们也可以自定义异常处理来改表默认的客户端访问接口产生的异常信息。

我们手动定义一个CalculateErrorException,继承RuntimeException。

java 复制代码
public class CalculateErrorException extends RuntimeException{
    private static final long serialVersionUID = -1L;
    private String id;
    public CalculateErrorException(String id){
        super("calculate is error");
        this.id = id;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}

然后定义一个Controller异常处理类ControllerExceptionHandler:

java 复制代码
@ControllerAdvice
public class ControllerExceptionHandler {
    @ExceptionHandler(CalculateErrorException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, Object> handleUserNotExistsException(CalculateErrorException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("id", e.getId());
        map.put("message", e.getMessage());
        return map;
    }
}

其中注解@ExceptionHandler指定了要处理的异常类型,注解@ResponseStatus指定异常处理方法返回的HTTP状态码为HttpStatus.INTERNAL_SERVER_ERROR,即500。HttpStatus是一个spring自带的枚举类型,封装了常见的HTTP状态码及描述:

java 复制代码
public enum HttpStatus {
    CONTINUE(100, "Continue"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols"),
    PROCESSING(102, "Processing"),
    CHECKPOINT(103, "Checkpoint"),
    OK(200, "OK"),
    CREATED(201, "Created"),
    ACCEPTED(202, "Accepted"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
    NO_CONTENT(204, "No Content"),
    RESET_CONTENT(205, "Reset Content"),
    PARTIAL_CONTENT(206, "Partial Content"),
    MULTI_STATUS(207, "Multi-Status"),
    ALREADY_REPORTED(208, "Already Reported"),
    IM_USED(226, "IM Used"),
    MULTIPLE_CHOICES(300, "Multiple Choices"),
    MOVED_PERMANENTLY(301, "Moved Permanently"),
    FOUND(302, "Found"),

    /** @deprecated */
    @Deprecated
    MOVED_TEMPORARILY(302, "Moved Temporarily"),
    SEE_OTHER(303, "See Other"),
    NOT_MODIFIED(304, "Not Modified"),

    /** @deprecated */
    @Deprecated
    USE_PROXY(305, "Use Proxy"),
    TEMPORARY_REDIRECT(307, "Temporary Redirect"),
    PERMANENT_REDIRECT(308, "Permanent Redirect"),
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "Unauthorized"),
    PAYMENT_REQUIRED(402, "Payment Required"),
    FORBIDDEN(403, "Forbidden"),
    NOT_FOUND(404, "Not Found"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
    NOT_ACCEPTABLE(406, "Not Acceptable"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
    REQUEST_TIMEOUT(408, "Request Timeout"),
    CONFLICT(409, "Conflict"),
    GONE(410, "Gone"),
    LENGTH_REQUIRED(411, "Length Required"),
    PRECONDITION_FAILED(412, "Precondition Failed"),
    PAYLOAD_TOO_LARGE(413, "Payload Too Large"),

    /** @deprecated */
    @Deprecated
    REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
    URI_TOO_LONG(414, "URI Too Long"),

    /** @deprecated */
    @Deprecated
    REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
    EXPECTATION_FAILED(417, "Expectation Failed"),
    I_AM_A_TEAPOT(418, "I'm a teapot"),

    /** @deprecated */
    @Deprecated
    INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"),

    /** @deprecated */
    @Deprecated
    METHOD_FAILURE(420, "Method Failure"),

    /** @deprecated */
    @Deprecated
    DESTINATION_LOCKED(421, "Destination Locked"),
    UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
    LOCKED(423, "Locked"),
    FAILED_DEPENDENCY(424, "Failed Dependency"),
    UPGRADE_REQUIRED(426, "Upgrade Required"),
    PRECONDITION_REQUIRED(428, "Precondition Required"),
    TOO_MANY_REQUESTS(429, "Too Many Requests"),
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
    UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
    NOT_IMPLEMENTED(501, "Not Implemented"),
    BAD_GATEWAY(502, "Bad Gateway"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
    VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
    INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
    LOOP_DETECTED(508, "Loop Detected"),
    BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
    NOT_EXTENDED(510, "Not Extended"),
    NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
    ...
}

编写完自定义异常处理逻辑后,我们将CalculateController中的方法抛出的异常改为CalculateErrorException:

java 复制代码
    @GetMapping("/{divisor:\\d+}/{dividend:\\d+}")
    public void calculate(@PathVariable Long divisor,@PathVariable Long dividend) {
        throw new CalculateErrorException("500");
    }

重启项目,再次访问http://localhost:8080/calculater/1/0,响应如下:

相关推荐
君顾11 小时前
智慧场馆解决方案小程序系统开发实战指南
java·开发语言·智慧场馆
AI多Agent协作实战派1 小时前
AI多Agent协作系统实战(四十五):漏声明了一个变量,整个页面的按钮都死了
后端
学习星球1 小时前
【LeetCode算法题精讲】二分查找精讲
java·数据结构·算法·leetcode·职场和发展·图搜索
Escalating_xu1 小时前
【Linux线程同步】从数据竞争到 mutex、条件变量与生产者消费者(上篇)
android·java·linux
叫我Paul就好1 小时前
当你用过 Spring,你可能就更能理解 DSH 的核心 Cordis
后端·面试
承渊政道2 小时前
10:30提交预约会不会撞上10:00的会议?我用飞算JavaAI3.9.1和3.9.9跑了四个时间段
java·springboot·ai编程·飞算javaai·java代码生成
SL_staff2 小时前
风控规则如何从硬编码解耦?我们在 JVS-Rules 中的实践与思考
java·spring boot·设计模式
NeverSettle_2 小时前
Agent 如何快速调用公司接口?——CLI + Skill 实践与踩坑
前端·javascript·后端
环境栈笔记2 小时前
指纹浏览器怎么用:从 Profile、代理到环境检测的完整上手流程
前端·人工智能·后端·自动化