SpringBoot 统一异常处理
在使用 SpringBoot 开发接口时,经常会遇到异常,比如数据库操作错误导致的异常。如果不对这些异常进行处理,异常日志就会直接传递到前端,一方面会泄露系统内部的信息;另一方面也会影响前端判断接口调用是否成功(不是统一的响应格式)。
但是如果在每个接口中处理异常的话,不仅很麻烦,而且产生了大量的冗余代码,不容易维护。因此,我们可以建立一个全局的异常处理器。
SpringWeb 提供了 @ControllerAdvice
注解,可以用来创建统一的异常处理器。
模拟异常
我们新建一个 UserController
,再创建 /user/login
接口,直接返回一个 RuntimeException
来模拟异常情况。
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login() {
throw new RuntimeException("An exception happened");
}
}
此时直接访问此接口会得到以下页面:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Apr 23 15:53:45 CST 2020
There was an unexpected error (type=Internal Server Error, status=500).
an exception happened
java.lang.RuntimeException: an exception happened
at com.example.demo.UserController.login(UserController.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
......
创建异常处理器
我们使用 @ControllerAdvice
和 @ExceptionHandler
来创建全局的异常处理器。
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(RuntimeException.class)
@ResponseBody
Message handleRuntimeException(RuntimeException e) {
LOGGER.error(e.getMessage(), e);
return ResultUtil.error(e.getMessage());
}
}
其中 @ControllerAdvice
注解指定了处理器的作用范围,@ExceptionHandler
注解指定了可以处理的异常类型。
测试异常处理器
此时再访问接口就可以得到经过处理的结果:
{
"code": "4000",
"msg": "An exception happened"
}
这样既不会泄露系统运行轨迹也方便了前端调用接口。
常见用途
自定义异常
我们可以自定义异常,再利用全局异常处理器进行处理,就可以根据自己的业务情况,方便地在需要的地方直接抛出异常。
自定义异常:
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
异常处理器:
@ExceptionHandler(CustomException.class)
@ResponseBody
Message handleCustomException(CustomException e) {
LOGGER.error(e.getMessage(), e);
return ResultUtil.error(e.getMessage());
}
模拟异常:
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login() {
throw new CustomException("这是一个自定义异常");
}
}
访问结果:
{
"code": "4000",
"msg": "这是一个自定义异常"
}
自动验证参数
我们可以将其和 @Valid
注解结合起来使用,实现自动验证请求参数。
定义 Bean:在需要验证的属性上添加相应的注解
public class UserBean {
@NotNull(message = "username 不能为空")
String username;
@NotNull(message = "password 不能为空")
String password;
// getters and setters
}
自动验证:使用 @Valid 注解,要求进行验证
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public Message login(@Valid @RequestBody UserBean user) {
System.out.println(user.getUsername());
return ResultUtil.success(user);
}
}
异常处理器:自动捕获验证异常,并处理
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
Message handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
LOGGER.error(e.getMessage(), e);
return ResultUtil.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
测试结果:
{
"code": "4000",
"msg": "password 不能为空"
}