简介

说明

        本文用实例介绍SpringBoot如何进行全局异常处理。

方案简述

         全局异常处理可以用两个注解来实现:@ControllerAdvice+@ExceptionHandler。这样可以捕获 Controller中抛出的指定类型异常,可对不同类型的异常单独进行处理。

系列文章

SpringBoot全局处理我写了一个系列:

  1. SpringBoot--全局异常处理--方法/实例_IT利刃出鞘的博客
  2. SpringBoot--全局响应处理--方法/实例_IT利刃出鞘的博客
  3. SpringBoot--全局请求处理--方法/实例_IT利刃出鞘的博客
  4. SpringBoot--全局格式处理--方法/实例_IT利刃出鞘的博客

@ControllerAdvice介绍

说明

        @ControllerAdvice里边有@Component,用于类,可包含@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。(因此是全局的处理方法)。

        可以在多个类上使用@ControllerAdvice,可通过@Order来控制顺序。不能通过实现Order接口来控制顺序,因为这部分源码里只支持@Order这种方式。见:调整多个ControllerAdvice的执行顺序_felixu的博客

        在Spring4及之后, @ControllerAdvice支持配置控制器的子集,可通过annotations(), basePackageClasses(), basePackages()方法选择控制器子集。

官网网址

​springmvc 注解总结 - SpringMVC中文官网​

使用场景

  1. @ControllerAdvice+@ExceptionHandler:全局异常处理
  1. 捕获Controller中抛出的指定类型异常,可对不同类型的异常区别处理
  1. @ControllerAdvice+ 实现ResponseBodyAdvice接口:全局响应处理(返回值)
  1. 其标注的方法将会在目标Controller方法执行之后执行。
  1. @ControllerAdvice+@ModelAttribute:全局请求处理
  1. 其标注的方法将会在目标Controller方法执行之前执行。
  2. 使用场景示例:鉴权/授权、全局处理格式
  1. @ControllerAdvice+@InitBinder:全局请求处理
  1. 其标注的方法将会在目标Controller方法执行之前执行。
  2. request中自定义参数解析方式进行注册
  3. 使用场景示例:全局处理格式
    SpringBoot--LocalDateTime--全局格式转换/前后端/前端入参/响应给前端_IT利刃出鞘的博客

实例:简单测试

代码

返回值用任何类型都可以,类似Controller的返回值。

package com.example.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
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.ControllerAdvice;

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

@Slf4j
@ControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler({Exception.class})
@ResponseBody
public Object handleException(HttpServletRequest request, Exception e) throws Exception {
log.error(e.getMessage(), e);
// 如果某个自定义异常有@ResponseStatus注解,就继续抛出
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
HashMap<String, Object> map = new HashMap<>();
map.put("message", "全局异常处理" + e.getMessage());
return map;
}
}

controller

package com.example.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@GetMapping("/advice")
public String test() {
int i = 1 / 0;
return "test success";
}
}

测试

访问:​​http://localhost:8080/advice​

前端结果:

{
"message": "全局异常处理/ by zero"
}

后端结果:

2021-08-18 00:39:20.240 ERROR 31064 --- [nio-8080-exec-1] c.example.config.GlobalExceptionAdvice   : / by zero

java.lang.ArithmeticException: / by zero
at com.example.controller.HelloController.test(HelloController.java:11) ~[classes/:na]
...

实例:项目实战

代码

异常处理类

package com.example.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestControllerAdvice
// 也可以:类上加@ControllerAdvice + 方法上加@ResponseBody
public class GlobalExceptionAdvice {
// 处理其他所有异常
@ExceptionHandler(Exception.class)
public Result<Object> handleException(Exception e) throws Exception {
log.info("这是异常处理");
log.error(e.getMessage(), e);

// 如果某个自定义异常有@ResponseStatus注解,就继续抛出
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}

// 实际项目中应该这样写,防止用户看到详细的异常信息
// return new Result().failure().message.message("操作失败");
return new Result<>().failure().message(e.getMessage());
}

// 处理自定义异常
@ExceptionHandler(BusinessException.class)
public Result<Object> handleBusinessException(Exception e) throws Exception {
log.error(e.getMessage(), e);

// 如果某个自定义异常有@ResponseStatus注解,就继续抛出
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}

// 实际项目中应该这样写,防止用户看到详细的异常信息
// return new Result<>().failure().message("操作失败");
return new Result<>().failure().message(e.getMessage());
}
}

或者这么写

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
// 也可以:类上加@ControllerAdvice + 方法上加@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e){
log.error(e.getMessage(), e);

// 如果某个自定义异常有@ResponseStatus注解,就继续抛出
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}

// 处理所有业务异常
if (e instanceOf BusinessException.class){
return Result.failure().message(e.getMessage());
}

// 防止用户看到详细的异常信息
return Result.failure().message("操作失败");
}
}

Controller(无需处理异常了)

package com.example.business.controller;

import com.example.business.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("user")
public class UserController {

@GetMapping("fault")
public void fault() {
int i = 1 / 0;
}

}

测试

postman访问:​​http://localhost:8080/user/fault​

postman结果

SpringBoot--全局异常处理--方法/实例_异常处理


后端结果

SpringBoot--全局异常处理--方法/实例_spring boot_02


公共代码

下边这两个类好几处都用到了,单独把它提出来。

 User实体类

package com.example.business.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {
private Long id;
private String userName;
private Integer age;
}

响应的包装

package com.example.demo.common.entity;

import lombok.Data;

@Data
public class Result<T> {
private boolean success = true;

private int code = 1000;

private String message;

private T data;

public Result() {
}

public Result(boolean success) {
this.success = success;
}

public Result<T> success(boolean success) {
Result<T> result = new Result<>(success);
if (success) {
result.code = 1000;
} else {
result.code = 1001;
}
return result;
}

public Result<T> success() {
return success(true);
}

public Result<T> failure() {
return success(false);
}

/**
* @param code {@link ResultCode#getCode()}
*/
public Result<T> code(int code) {
this.code = code;
return this;
}

public Result<T> message(String message) {
this.message = message;
return this;
}

public Result<T> data(T data) {
this.data = data;
return this;
}
}

自定义异常

package com.example.common.exception;

public class BusinessException extends RuntimeException{
public BusinessException() {
super();
}

public BusinessException(String message) {
super(message);
}

public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}