关于Web应用的全局异常处理,上一篇介绍了 ControllerAdvice
结合 @ExceptionHandler
的方式来实现web应用的全局异常管理;
本篇博文则带来另外一种并不常见的使用方式,通过实现自定义的 HandlerExceptionResolver
,来处理异常状态
上篇博文链接: SpringBoot系列教程web篇之全局异常处理
首先得搭建一个web应用才有可能继续后续的测试,借助SpringBoot搭建一个web应用属于比较简单的活;
创建一个maven项目,pom文件如下
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7</version> <relativePath/> <!-- lookup parent from update --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
HandlerExceptionResolver
顾名思义,就是处理异常的类,接口就一个方法,出现异常之后的回调,四个参数中还携带了异常堆栈信息
@Nullable ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
我们自定义异常处理类就比较简单了,实现上面的接口,然后将完整的堆栈返回给调用方
public class SelfExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String msg = GlobalExceptionHandler.getThrowableStackInfo(ex); try { response.addHeader("Content-Type", "text/html; charset=UTF-8"); response.getWriter().append("自定义异常处理!!! /n").append(msg).flush(); } catch (Exception e) { e.printStackTrace(); } return null; } } // 堆栈信息打印方法如下 public static String getThrowableStackInfo(Throwable e) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); e.printStackTrace(new java.io.PrintWriter(buf, true)); String msg = buf.toString(); try { buf.close(); } catch (Exception t) { return e.getMessage(); } return msg; }
仔细观察上面的代码实现,有下面几个点需要注意
response.addHeader("Content-Type", "text/html; charset=UTF-8");
如果没有这一行,会出现中文乱码的情况 response.getWriter().append("自定义异常处理!!! /n").append(msg).flush();
; 如果项目中有自定义的错误页面,可以通过返回 ModelAndView
来确定最终返回的错误页面 WebMvcConfigurer
的子类中实现注册,实例如下 @SpringBootApplication public class Application implements WebMvcConfigurer { @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(0, new SelfExceptionHandler()); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
我们依然使用上篇博文的用例来测试
@Controller @RequestMapping(path = "page") public class ErrorPageRest { @ResponseBody @GetMapping(path = "divide") public int divide(int sub) { return 1000 / sub; } }
下面分别是404异常和500异常的实测情况
500异常会进入我们的自定义异常处理类, 而404依然走的是默认的错误页面,所以如果我们需要捕获404异常,依然需要在配置文件中添加
# 出现错误时, 直接抛出异常 spring.mvc.throw-exception-if-no-handler-found=true # 设置静态资源映射访问路径 spring.mvc.static-path-pattern=/statics/** # spring.resources.add-mappings=false
下面尽量以通俗易懂的方式说明下这个问题
@ResponseBody
来表明一个url返回的是json数据(通常情况下是这样的,不考虑自定义实现) @Controller
中通过 @RequestMapping
定义的REST服务,返回的是静态资源 NoHandlerFoundException
,不抛异常,而是到静态资源中去找了(静态资源中也没有,为啥不抛NoHandlerFoundException呢?这个异常表示这个url请求没有对应的处理器,但是我们这里呢,给它分配到了静态资源处理器了 ResourceHttpRequestHandler
) 针对上面这点,如果有兴趣深挖的同学,这里给出关键代码位置
// 进入方法: `org.springframework.web.servlet.DispatcherServlet#doDispatch` // debug 节点 Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 核心逻辑 // org.springframework.web.servlet.DispatcherServlet#getHandler @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null; }
本篇博文虽然也介绍了一种新的全局异常处理方式,实现效果和 ControllerAdvice
也差不多,但是并不推荐用这种方法, 原因如下
HandlerExceptionResolver
的方式没有 ControllerAdvice
方式简介优雅 DefaultHandlerExceptionResolver
已经非常强大了,基本上覆盖了http的各种状态码,我们自己再去定制的必要性不大 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
打赏 如果觉得我的文章对您有帮助,请随意打赏。