一、前言

SpringBoot框架对异常的处理提供了几种很强大的方法,我们可以通过@ControllerAdvice和@ExceptionHandler注解实现全局异常的处理,也可以通过实现HandlerExceptionResolve接口来完成全局异常的处理。

二、全局异常处理方式一

通过@ControllerAdvice和@ExceptionHandler注解实现全局异常拦截,它可以拦截controller层请求方法抛出的异常信息,同时外加@ ResponseBody注解,可以实现响应类型为json格式。或者直接使用@RestControllerAdvice和@ExceptionHandler注解的方式实现响应类型为json格式的数据。

1.添加依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
  • 1.
  • 2.
  • 3.
  • 4.

2.自定义异常类

package com.example.dataproject.exception;

/**
 * @author qx
 * @date 2024/8/8
 * @des 自定义异常
 */
public class ServiceException extends RuntimeException {

    private Integer code;

    public Integer getCode() {
        return code;
    }

    public ServiceException(String message, Integer code) {
        super(message);
        this.code = code;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

3.全局异常处理类

包含对自定义异常和空指针异常的处理。

package com.example.dataproject.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qx
 * @date 2024/8/8
 * @des 全局异常处理类
 */
@RestControllerAdvice
@Slf4j
public class GlobalException {


    @ExceptionHandler(value = {Exception.class})
    public Map<String, Object> exceptionHandler(HttpServletRequest request, Exception e) {
        log.info("未知异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", 999);
        map.put("message", e.getMessage());
        return map;
    }

    @ExceptionHandler(value = {ServiceException.class})
    public Map<String, Object> serviceExceptionHandler(HttpServletRequest request, ServiceException e) {
        log.info("自定义异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("message", e.getMessage());
        return map;
    }

    @ExceptionHandler(value = {NullPointerException.class})
    public Map<String, Object> nullPointExceptionHandler(HttpServletRequest request, NullPointerException e) {
        log.info("空指针异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("message", e.getMessage());
        return map;
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

4.创建控制层测试

package com.example.dataproject.controller;

import com.example.dataproject.exception.ServiceException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2024/8/8
 * @des 测试
 */
@RestController
public class IndexController {


    @GetMapping("/null")
    public String testNull() {
        String s = null;
        //抛出空指针异常 全局异常中的空指针异常处理会捕获到这个异常
        if (true) {
            throw new NullPointerException("空指针异常");
        }
        return "null success";
    }

    @GetMapping("/service")
    public String testService() {
        if (true) {
            //抛出自定义异常 全局异常中的自定义异常处理会捕获到这个异常
            throw new ServiceException("自定义服务异常", 888);
        }
        return "service success";
    }

    @GetMapping("/exception")
    public String testException() {
        if (true) {
            throw new RuntimeException("其他异常");
        }
        return "exception success";
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

5.启动程序并访问请求进行测试

测试空指针异常

SpringBoot优雅捕捉异常的两种方法_@ExceptionHandler

测试自定义服务异常

SpringBoot优雅捕捉异常的两种方法_SpringBoot_02

其他异常

SpringBoot优雅捕捉异常的两种方法_@ExceptionHandler_03

6.404异常特殊处理

默认情况下,@ExceptionHandler注解无法捕捉到 404 异常,比如请求一个无效的地址,返回信息如下:

SpringBoot优雅捕捉异常的两种方法_@ControllerAdvice_04

如果想要捕捉到这种异常,可以在application.properties文件中添加如下配置来实现。

# 如果没有找到请求地址,抛异常
spring.mvc.throw-exception-if-no-handler-found=true
# 关闭默认的静态资源路径映射
spring.web.resources.add-mappings=false
  • 1.
  • 2.
  • 3.
  • 4.

启动服务,再次发起地址请求,结果如下:

SpringBoot优雅捕捉异常的两种方法_SpringBoot_05

7.自定义异常页面的实现

某些场景下,当发生异常时希望跳转到自定义的异常页面,如何实现呢?

首先,这里基于thymeleaf模板引擎来开发页面,在templates目录下创建一个异常页面error.html。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误</title>
</head>
<body>
出错啦,请与管理员联系<br>
错误详情:<span th:text="${message}"></span>
</body>
</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

我们重新修改一下全局异常处理类,让异常返回结果到页面中。

package com.example.dataproject.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qx
 * @date 2024/8/8
 * @des 全局异常处理类
 */
@ControllerAdvice
@Slf4j
public class GlobalException {


    @ExceptionHandler(value = {Exception.class})
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) {
        log.info("未知异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
      /*  Map<String, Object> map = new HashMap<>();
        map.put("code", 999);
        map.put("message", e.getMessage());
        return map;*/
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message", e.getMessage());
        return modelAndView;
    }

    @ExceptionHandler(value = {ServiceException.class})
    @ResponseBody
    public Map<String, Object> serviceExceptionHandler(HttpServletRequest request, ServiceException e) {
        log.info("自定义异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("message", e.getMessage());
        return map;
    }

    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public Map<String, Object> nullPointExceptionHandler(HttpServletRequest request, NullPointerException e) {
        log.info("空指针异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("message", e.getMessage());
        return map;
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

我们重新请求刚才不存在的访问时,这个时候跳转到了页面,并在页面中显示了异常的信息。

SpringBoot优雅捕捉异常的两种方法_@ExceptionHandler_06

三、全局异常处理方式二

在 Spring Boot 中,除了通过@ControllerAdvice和@ExceptionHandler注解实现全局异常处理外,还有一种通过实现HandlerExceptionResolver接口来完成全局异常的处理。

具体实现示例如下:

package com.example.dataproject.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author qx
 * @date 2024/8/8
 * @des 全局异常处理
 */
@Component
@Slf4j
public class GlobalExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        log.error("接口请求出现异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        if (e instanceof NullPointerException) {
            // 设置响应类型为json格式
            ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
            mv.addObject("code", 500);
            mv.addObject("msg", e.getMessage());
            return mv;
        } else {
            // 设置响应类型为错误页面
            ModelAndView mv = new ModelAndView();
            mv.addObject("message", e.getMessage());
            mv.setViewName("error");
            return mv;
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

如果是空指针异常的话会返回json数据格式,如果是其他异常会在页面上显示异常的信息。

其他异常

SpringBoot优雅捕捉异常的两种方法_@ExceptionHandler_07

空指针异常

SpringBoot优雅捕捉异常的两种方法_@ControllerAdvice_08

虽然这种方式能够处理全局异常,但是 Spring 官方不推荐使用它;同时实测过程中发现它无法拦截 404 错误,当请求错误地址时,会优先被DefaultHandlerExceptionResolver默认异常处理类拦截,自定义的异常处理类无法捕捉。