一、前言
SpringBoot框架对异常的处理提供了几种很强大的方法,我们可以通过@ControllerAdvice和@ExceptionHandler注解实现全局异常的处理,也可以通过实现HandlerExceptionResolve接口来完成全局异常的处理。
二、全局异常处理方式一
通过@ControllerAdvice和@ExceptionHandler注解实现全局异常拦截,它可以拦截controller层请求方法抛出的异常信息,同时外加@ ResponseBody注解,可以实现响应类型为json格式。或者直接使用@RestControllerAdvice和@ExceptionHandler注解的方式实现响应类型为json格式的数据。
1.添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
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;
}
}
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;
}
}
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";
}
}
5.启动程序并访问请求进行测试
测试空指针异常
测试自定义服务异常
其他异常
6.404异常特殊处理
默认情况下,@ExceptionHandler注解无法捕捉到 404 异常,比如请求一个无效的地址,返回信息如下:
如果想要捕捉到这种异常,可以在application.properties文件中添加如下配置来实现。
# 如果没有找到请求地址,抛异常
spring.mvc.throw-exception-if-no-handler-found=true
# 关闭默认的静态资源路径映射
spring.web.resources.add-mappings=false
启动服务,再次发起地址请求,结果如下:
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>
我们重新修改一下全局异常处理类,让异常返回结果到页面中。
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;
}
}
我们重新请求刚才不存在的访问时,这个时候跳转到了页面,并在页面中显示了异常的信息。
三、全局异常处理方式二
在 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;
}
}
}
如果是空指针异常的话会返回json数据格式,如果是其他异常会在页面上显示异常的信息。
其他异常
空指针异常
虽然这种方式能够处理全局异常,但是 Spring 官方不推荐使用它;同时实测过程中发现它无法拦截 404 错误,当请求错误地址时,会优先被DefaultHandlerExceptionResolver默认异常处理类拦截,自定义的异常处理类无法捕捉。