
概述
异常处理注解
spring中处理异常可以通过2个注解:
@ControllerAdvice
全局,处理所有控制器中的异常@ExceptionHandler
局部,只针对某个控制器中的异常
先有ExceptionHandler
,再有ControllerAdvice
,ExceptionHandler
不能集中处理异常,ControllerAdvice
为弥补此缺点引入,推荐使用ControllerAdvice
。本文介绍ControllerAdvice
的用法,对ExceptionHandler
不作介绍,如需了解可参考相关资料。
错误处理页面:ErrorController
ErrorController
的作用是为servlet设置错误页,默认错误页是Whitelabel,访问不存在的页面就会显示此错误页。
通过继承ErrorController
接口可以设置自定义的错误页。
@RestController public class MyErrorController implements ErrorController { @RequestMapping(value = "/error") public ResponseEntity<Result> error() { Result res = new Result(404, "页面未找到"); return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND); } @Override public String getErrorPath() { return "/error"; } }
如果ControllerAdvice
中没有直接返回http响应,继续抛出异常,将会调用ErrorController
显示错误页。如果在ControllerAdvice
中捕获异常并直接返回http响应,就没必要配置ErrorController
中的错误页了。
404 异常
spring boot默认不会抛出404异常(NoHandlerFoundException),所以在ControllerAdvice
中捕获不到该异常,导致404总是跳过ContollerAdvice
,直接显示ErrorController
的错误页。需要改变配置,让404错误抛出异常(NoHandlerFoundException),这样便可在ControllerAdvice
中捕获此异常。改变配置,在application.properties
中添加:
spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false
过滤器异常
ContollerAdvice
不能捕获过滤器抛出的异常,对于此类异常需要特别处理。
如前篇教程的jwt过滤器,异常处理需要设置特别的处理类。
使用ContollerAdvice
可以实现对所有控制器异常的集中处理,下面通过一个实际项目介绍此过程。
项目内容
模拟一个用户注册的接口,抛出各类异常让ContollerAdvice
处理。
要求
- JDK1.8或更新版本
- Eclipse开发环境
如没有开发环境,可参考spring boot 开发环境搭建(Eclipse)。
项目创建
创建spring boot项目
打开Eclipse,创建spring boot的spring starter project项目,选择菜单:File > New > Project ...
,弹出对话框,选择:Spring Boot > Spring Starter Project
,在配置依赖时,勾选web
,完成项目创建。
项目配置
application.properties配置
## 服务器端口,默认是8080 server.port=8096 ## 让404错误抛出异常,需要同时设置spring.resources.add-mappings为false # 让404错误抛出异常 spring.mvc.throw-exception-if-no-handler-found=true # 禁用静态资源的自动映射,如不禁用,不存在的url将被映射到/**,servlet不有机会抛出异常 spring.resources.add-mappings=false ## log级别设置为debug, 通过log.debug打印异常信息 logging.level.root=DEBUG
如果使用静态资源的自动映射,不存在的url将被映射到/**,servlet不有机会抛出异常。在rest api的项目中没有静态资源的处理,完全可以禁止。
代码实现
项目目录结构如下图,我们添加了几个类,下面将详细介绍。
异常处理类 ErrorHandler.java
这个类就是加ControllerAdvice
注解的异常处理类,所有控制器的异常,都在这里集中处理。这里我们实现了2类特殊异常的处理函数,以及1个缺省的异常处理函数。
- 输入参数校验异常处理
- 404异常处理
- 缺省的异常处理函数,处理所有其他异常
@ControllerAdvice //表明这是控制器的异常处理类 public class ErrorHandler { private static final org.slf4j.Logger log = LoggerFactory.getLogger(ErrorHandler.class); /** * 输入参数校验异常 */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public ResponseEntity<Result> NotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception { log.debug("异常详情", e); BindingResult bindingResult = e.getBindingResult(); //rfc4918 - 11.2. 422: Unprocessable Entity Result res = MiscUtil.getValidateError(bindingResult); return new ResponseEntity<Result>(res, HttpStatus.UNPROCESSABLE_ENTITY); } /** * 404异常处理 */ @ExceptionHandler(value = NoHandlerFoundException.class) public ResponseEntity<Result> NoHandlerFoundExceptionHandler(HttpServletRequest req, Exception e) throws Exception { log.debug("异常详情", e); Result res = new Result(404, "页面不存在"); return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND); } /** * 默认异常处理,前面未处理 */ @ExceptionHandler(value = Throwable.class) public ResponseEntity<Result> defaultHandler(HttpServletRequest req, Exception e) throws Exception { Result res = new Result(500, "服务器内部错误"); log.debug("异常详情", e); return new ResponseEntity<Result>(res, HttpStatus.INTERNAL_SERVER_ERROR); } }
输入参数校验异常处理,在教程spring boot输入数据校验(validation)里,是直接在控制器返回bindingResult
,现在放在这里统一处理,好处是无需再在每个接口里重复写返回bindingResult
的代码。
控制器 AuthController
AuthController实现了一个模拟用户注册的接口,数据校验返回bindingResult
的代码被去除,spring boot将直接抛出MethodArgumentNotValidException,然后由ErrorHandler捕获处理。
@RestController @RequestMapping("/auth") public class AuthController { @RequestMapping(value = "/register", method = RequestMethod.POST, produces="application/json") public ResponseEntity<Result> register( @Valid @RequestBody RegisterRequest register // , BindingResult bindingResult ) throws Exception { // if(bindingResult.hasErrors()) { // // Result res1 = MiscUtil.getValidateError(bindingResult); // return new ResponseEntity<Result>(res1, HttpStatus.UNPROCESSABLE_ENTITY); // } Result res = new Result(200, "ok"); return ResponseEntity.ok(res); } }
RegisterRequest (DTO/数据传输对象)
RegisterRequest类接受并校验用户注册时输入的信息。关于数据校验可参考教程spring boot输入数据校验(validation)。
public class RegisterRequest { @SuppressWarnings("unused") private static final org.slf4j.Logger log = LoggerFactory.getLogger(RegisterRequest.class); @NotNull(message="手机号必须填") @Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message="账号请输入11位手机号") // 手机号 private String mobile; @NotNull(message="昵称必须填") @Size(min=1, max=20, message="昵称1~20个字") private String nickname; @NotNull(message="密码必须填") @Size(min=6, max=16, message="密码6~16位") private String password; public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
辅助类
- Result 结果封装类
- MiscUtil 杂项功能
运行
Eclipse左侧,在项目根目录上点击鼠标右键弹出菜单,选择:run as -> spring boot app
运行程序。 打开Postman访问接口,运行结果如下:
用户注册,输入错误的信息
访问不存在的网址
总结
完成。由于作者水平有限,错漏缺点在所难免,欢迎批评指正。