SpringBoot为异常处理提供了很多优秀的方法,但是像我这种新手在处理异常时还是会觉得一头包,终于我痛定思痛,总结了一下统一异常处理的办法,让异常处理变得上流。
本片文章较长,我先定义以下后面会用到的类
/** * @author ht113 * * 错误dto,就是从后端返回到前端的错误信息,下面这个作为例子比较简单 */ @Data public class ErrorInfo { String datetime; String msg; String url; HttpStatus httpStatus; public ErrorInfo(String datetime, String message, String url, HttpStatus httpStatus) { this.datetime = datetime; this.msg = message; this.url = url; this.httpStatus = httpStatus; } }自定义抽象异常类
/** * @author ht113 * * * 基本抽象类,所以我自定义的异常类都继承自此方法 */ public abstract class AbstractBaseException extends Exception{ private ErrorInfo errorInfo; public AbstractBaseException(String msg){ super(msg); } public AbstractBaseException(String msg, Throwable cause){ super(msg, cause); } public void setErrorInfo(ErrorInfo errorInfo){ this.errorInfo = errorInfo; } //这是唯一的抽象方法,也是子类必须实现的方法,以此达到使自定义异常对应到不同的http状态 @NonNull public abstract HttpStatus getHttpStatus(); }以下两个是自定义的异常类,对应的http状态分别为NOT_FOUND和INTERNAL_SERVER_ERROR,即404和500
public class MyExceptionA extends AbstractBaseException { public MyExceptionA(String msg) { super(msg); } public MyExceptionA(String msg, Throwable cause){ super(msg, cause); } @Override public HttpStatus getHttpStatus() { return HttpStatus.NOT_FOUND; } } public class MyExceptionB extends AbstractBaseException { public MyExceptionB(String msg) { super(msg); } public MyExceptionB(String msg, Throwable cause){ super(msg, cause); } @Override public HttpStatus getHttpStatus() { return HttpStatus.INTERNAL_SERVER_ERROR; } }在Spring中,我们可以通过对Controller中的方法添加@ExceptionHandler注释来实现对同一个Controller中路由方法抛出的异常进行处理,来达到我们想要的异常处理效果
/** * @author ht113 */ @RestController public class HelloController { @RequestMapping("/") public String hello() throws AbstractBaseException{ throw new MyExceptionA("exception A test"); } //需要捕获的异常的类 @ExceptionHandler(MyExceptionA.class) public String exceptionHandlerA(){ return "exception A caught successfully"; } }处理效果:
是个好方法,但是有个致命缺点,就是处理方法不能在Controller间共享,如果我在另一个Controller中抛出自定义异常就要重新实现一次,太拉垮了。
/** * @author ht113 */ @RestController public class HelloController2 { @RequestMapping("/hello2") public String hello2() throws AbstractBaseException { throw new MyExceptionA("exception A test"); } }通过对Controller添加注释@ControllerAdvice可以实现Controller层的全局异常处理,处理方法和@ExceptionHandler相同,但是可以在Controller中共享,实例如下:
/** * @author ht113 */ @ControllerAdvice public class MyHandler { @ExceptionHandler(AbstractBaseException.class) public ResponseEntity<ErrorInfo> handler(HttpServletRequest request, AbstractBaseException e){ return ResponseEntity.status(e.getHttpStatus()).body( new ErrorInfo(new Date().toString(), e.getMessage(), request.getRequestURL().toString(), e.getHttpStatus())); } }测试类
/** * @author ht113 */ @RestController public class HelloController { @RequestMapping("/1/testA") public void hello() throws AbstractBaseException{ throw new MyExceptionA("exception A test from Controller1"); } @RequestMapping("/1/testB") public void hello1() throws AbstractBaseException{ throw new MyExceptionB("exception B test from Controller1"); } } /** * @author ht113 */ @RestController public class HelloController2 { @RequestMapping("/2/testA") public void hello() throws AbstractBaseException { throw new MyExceptionA("exception A test from Controller2"); } @RequestMapping("/2/testB") public void hello1() throws AbstractBaseException{ throw new MyExceptionB("exception B test from Controller2"); } }测试结果
我们所有的自定义异常,只要继承自AbstractBaseException都可以被hander()方法捕获,进行统一异常处理,当然也可以根据异常的不同进行特定的处理,只需要在MyHandler类中添加新的处理方法即可
过滤器Filter和过滤器Interceptor拦截器的使用总是后端开发不可避免的一部分,有时候我们需要在过滤器或拦截器中进行逻辑处理,抛出异常,那么这里抛出的异常可以被@ControllerAdvice注释的异常处理类捕获吗?答案是Filter不会,而Interceptor可以,Filter是基于Servlet框架编写的,Interceptor是基于SpringMVC框架的,SpingMVC不会对Filter中的异常统一处理,所以这里被抛出的异常不能被捕获,那我们如何让不能被捕获的异常被被捕获呢?
答案有两个:
直接在doFilterInternal()中处理异常,直接return给客户端通过HandlerExceptionResolver主动对异常进行捕获(当然了前面的Controller层的全局处理也可以用这种方式)本例选择方法2,便于实现真正的统一。
Filter、Interception、Controller处理流程:
用上面提到的testA进行测试,结果如下(如果Controller中抛出异常,那么postHandler不会调用):
HandlerExceptionResolver类图:
如何使用HandlerExceptionResolver?
如果自定义异常类,考虑加上 ResponseStatus 注解;对于没有 ResponseStatus注解的异常,可以通过使用 ExceptionHandler + ControllerAdvice注解,或者通过配置SimpleMappingExceptionResolver,来为整个Web应用提供统一的异常处理。如果应用中有些异常处理方式,只针对特定的Controller使用,那么在这个Controller中使用 ExceptionHandler 注解。不要使用过多的异常处理方式,不然的话,维护起来会很苦恼,因为异常的处理分散在很多不同的地方。用的是MyExceptionA,结果如下
由于ExceptionHandler可以直接捕获Interceptor中的异常,所以在Interceptor中自定义异常和Controller中一样使用就可以。
由于ExceptionHandler可以直接捕获Interceptor中的异常,所以在Interceptor中自定义异常和Controller中一样使用就可以。
OVER!