SpringBoot--全局异常处理--方法/实例
原创
©著作权归作者所有:来自51CTO博客作者IT利刃出鞘的原创作品,请联系作者获取转载授权,否则将追究法律责任
简介
说明
本文用实例介绍SpringBoot如何进行全局异常处理。
方案简述
全局异常处理可以用两个注解来实现:@ControllerAdvice+@ExceptionHandler。这样可以捕获 Controller中抛出的指定类型异常,可对不同类型的异常单独进行处理。
系列文章
SpringBoot全局处理我写了一个系列:
- SpringBoot--全局异常处理--方法/实例_IT利刃出鞘的博客
- SpringBoot--全局响应处理--方法/实例_IT利刃出鞘的博客
- SpringBoot--全局请求处理--方法/实例_IT利刃出鞘的博客
- SpringBoot--全局格式处理--方法/实例_IT利刃出鞘的博客
@ControllerAdvice介绍
说明
@ControllerAdvice里边有@Component,用于类,可包含@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。(因此是全局的处理方法)。
可以在多个类上使用@ControllerAdvice,可通过@Order来控制顺序。不能通过实现Order接口来控制顺序,因为这部分源码里只支持@Order这种方式。见:调整多个ControllerAdvice的执行顺序_felixu的博客
在Spring4及之后, @ControllerAdvice支持配置控制器的子集,可通过annotations(), basePackageClasses(), basePackages()方法选择控制器子集。
官网网址
springmvc 注解总结 - SpringMVC中文官网
使用场景
- @ControllerAdvice+@ExceptionHandler:全局异常处理
- 捕获Controller中抛出的指定类型异常,可对不同类型的异常区别处理
- @ControllerAdvice+ 实现ResponseBodyAdvice接口:全局响应处理(返回值)
- 其标注的方法将会在目标Controller方法执行之后执行。
- @ControllerAdvice+@ModelAttribute:全局请求处理
- 其标注的方法将会在目标Controller方法执行之前执行。
- 使用场景示例:鉴权/授权、全局处理格式
- @ControllerAdvice+@InitBinder:全局请求处理
- 其标注的方法将会在目标Controller方法执行之前执行。
- request中自定义参数解析方式进行注册
- 使用场景示例:全局处理格式
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结果
后端结果
公共代码
下边这两个类好几处都用到了,单独把它提出来。
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);
}
}