ExceptionHandler, 方法注解, 做用于 Controller 级别. ExceptionHandler 注解为一个 Controler 定义一个异常处理器.html
ControllerAdvice, 类注解, 做用于 整个 Spring 工程. ControllerAdvice 注解定义了一个全局的异常处理器.java
须要注意的是, ExceptionHandler 的优先级比 ControllerAdvice 高, 即 Controller 抛出的异常若是既可让 ExceptionHandler 标注的方法处理, 又可让 ControllerAdvice 标注的类中的方法处理, 则优先让 ExceptionHandler 标注的方法处理.
git
为了方便地展现 Controller 异常处理的方式, 我建立了一个工程 SpringBootRESTfulErrorHandler, 其源码能够到个人 Github: github.com/yongshun 中找到.
SpringBootRESTfulErrorHandler 工程的目录结构以下:github
首先咱们定义了三个自定义的异常:
BaseException:spring
public class BaseException extends Exception { public BaseException(String message) { super(message); } }
MyException1:segmentfault
public class MyException1 extends BaseException { public MyException1(String message) { super(message); } }
MyException2:浏览器
public class MyException2 extends BaseException { public MyException2(String message) { super(message); } }
接着咱们在 DemoController 中分别抛出这些异常:mvc
@RestController public class DemoController { private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler"); @RequestMapping("/ex1") public Object throwBaseException() throws Exception { throw new BaseException("This is BaseException."); } @RequestMapping("/ex2") public Object throwMyException1() throws Exception { throw new MyException1("This is MyException1."); } @RequestMapping("/ex3") public Object throwMyException2() throws Exception { throw new MyException2("This is MyException1."); } @RequestMapping("/ex4") public Object throwIOException() throws Exception { throw new IOException("This is IOException."); } @RequestMapping("/ex5") public Object throwNullPointerException() throws Exception { throw new NullPointerException("This is NullPointerException."); } @ExceptionHandler(NullPointerException.class) public String controllerExceptionHandler(HttpServletRequest req, Exception e) { logger.error("---ControllerException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return e.getMessage(); } }
/ex1: 抛出 BaseExceptionapp
/ex2: 抛出 MyException1ide
/ex3: 抛出 MyException2
/ex4: 抛出 IOException
/ex5: 抛出 NullPointerException
当 DemoController 抛出未捕获的异常时, 咱们在 GlobalExceptionHandler 中进行捕获并处理:
GlobalExceptionHandler:
@RestController @ControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler"); @ExceptionHandler(value = BaseException.class) @ResponseBody public Object baseErrorHandler(HttpServletRequest req, Exception e) throws Exception { logger.error("---BaseException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return e.getMessage(); } @ExceptionHandler(value = Exception.class) @ResponseBody public Object defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { logger.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return e.getMessage(); } }
咱们看到, GlobalExceptionHandler 类有两个注解:
RestController, 代表 GlobalExceptionHandler 是一个 RESTful Controller, 即它会以 RESTful 的形式返回回复.
ControllerAdvice, 表示 GlobalExceptionHandler 是一个全局的异常处理器.
在 GlobalExceptionHandler 中, 咱们使用了 ExceptionHandler 注解标注了两个方法:
ExceptionHandler(value = BaseException.class): 表示 baseErrorHandler 处理 BaseException 异常和其子异常.
ExceptionHandler(value = Exception.class): 表示 defaultErrorHandler 会处理 Exception 异常和其所用子异常.
要注意的是, 和 try...catch 语句块, 异常处理的顺序也是从具体到通常, 即若是 baseErrorHandler 能够处理此异常, 则调用此方法来处理异常, 反之使用 defaultErrorHandler 来处理异常.
既然咱们已经实现了 Controller 的异常处理, 那么接下来咱们就来测试一下吧.
在浏览器中分别访问这些连接, 结果以下:
/ex1:
/ex2:
/ex3:
/ex4:
/ex5:
能够看到, /ex1, /ex2, /ex3 抛出的异常都由 GlobalExceptionHandler.baseErrorHandler 处理; /ex4 抛出的 IOException 异常由 GlobalExceptionHandler.defaultErrorHandler 处理. 可是 /ex5 抛出的 NullPointerException 异常为何不是 defaultErrorHandler 处理, 而是由 controllerExceptionHandler 来处理呢? 回想到 @ControllerAdvice 和 @ExceptionHandler 的区别 这以小节中的内容时, 咱们就知道缘由了: 由于咱们在 DemoController 中使用 ExceptionHandler 注解定义了一个 Controller 级的异常处理器, 这个级别的异常处理器的优先级比全局的异常处理器优先级高, 所以 Spring 发现 controllerExceptionHandler 能够处理 NullPointerException 异常时, 就调用这个方法, 而不会调用全局的 defaultErrorHandler 方法了.
SpringBoot 默认提供了一个全局的 handler 来处理全部的 HTTP 错误, 并把它映射为 /error. 当发生一个 HTTP 错误, 例如 404 错误时, SpringBoot 内部的机制会将页面重定向到 /error 中.
例以下图中是一个默认的 SpringBoot 404 异常页面.
这个页面实在是太丑了, 咱们能不能自定义一个异常页面呢? 固然能够了, 而且 SpringBoot 也给咱们提示了: This application has no explicit mapping for /error, so you are seeing this as a fallback.
所以咱们实现一个 /error 映射的 Controller 便可.
public class HttpErrorHandler implements ErrorController { private final static String ERROR_PATH = "/error"; /** * Supports the HTML Error View * * @param request * @return */ @RequestMapping(value = ERROR_PATH, produces = "text/html") public String errorHtml(HttpServletRequest request) { return "404"; } /** * Supports other formats like JSON, XML * * @param request * @return */ @RequestMapping(value = ERROR_PATH) @ResponseBody public Object error(HttpServletRequest request) { return "404"; } /** * Returns the path of the error page. * * @return the error path */ @Override public String getErrorPath() { return ERROR_PATH; } }
根据上面代码咱们看到, 为了实现自定义的 404 页面, 咱们实现了 ErrorController 接口:
public interface ErrorController { String getErrorPath(); }
这个接口只有一个方法, 当出现 HTTP 错误时, SpringBoot 会将页面重定向到 getErrorPath 方法返回的页面中. 这样咱们就能够实现自定义的错误页面了.
提供一个自定义的 "/error" 页面对 Spring MVC 的服务来讲天然是没问题的, 可是若是咱们的服务是一个 RESTful 服务的话, 这样作就不行了.
当用户调用了一个不存在的 RESTful API 时, 咱们想记录下这个异常访问, 并返回一个表明错误的 JSON 给客户端, 这该怎么实现呢?
咱们很天然地想到, 咱们可使用处理异常的那一套来处理 404 错误码.
那么咱们来试一下这个想法是否可行吧.
奇怪的是, 当咱们在浏览器中随意输入一个路径时, 代码并无执行到异常处理逻辑中, 而是返回了一个 HTML 页面给咱们, 这又是怎么回事呢?原来 Spring Boot 中, 当用户访问了一个不存在的连接时, Spring 默认会将页面重定向到 **/error** 上, 而不会抛出异常.
既然如此, 那咱们就告诉 Spring Boot, 当出现 404 错误时, 抛出一个异常便可. 在 application.properties 中添加两个配置:
spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false
上面的配置中, 第一个 spring.mvc.throw-exception-if-no-handler-found 告诉 SpringBoot 当出现 404 错误时, 直接抛出异常. 第二个 spring.resources.add-mappings 告诉 SpringBoot 不要为咱们工程中的资源文件创建映射. 这两个配置正是 RESTful 服务所须要的.
当加上这两个配置后, 咱们再来试一下:
能够看到, 如今确实是在 defaultErrorHandler 中处理了.
本文由 yongshun 发表于我的博客, 采用署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议.
非商业转载请注明做者及出处. 商业转载请联系做者本人
Email: yongshun1228@gmail.com
本文标题为: SpringBoot RESTful 应用中的异常处理小结
本文连接为: http://www.javashuo.com/article/p-mhicgaab-dr.html