zoukankan      html  css  js  c++  java
  • SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

    关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理;

    本篇博文则带来另外一种并不常见的使用方式,通过实现自定义的HandlerExceptionResolver,来处理异常状态

    上篇博文链接: SpringBoot系列教程web篇之全局异常处理
    本篇原文: SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

    I. 环境搭建

    首先得搭建一个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>
    

    II. HandlerExceptionResolver

    1. 自定义异常处理

    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("自定义异常处理!!! 
    ").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的输出流中写入数据返回 response.getWriter().append("自定义异常处理!!! ").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);
        }
    }
    

    2. 测试case

    我们依然使用上篇博文的用例来测试

    @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
    

    为什么404需要额外处理?

    下面尽量以通俗易懂的方式说明下这个问题

    • java web应用,除了返回json类数据之外还可能返回网页,js,css
    • 我们通过 @ResponseBody来表明一个url返回的是json数据(通常情况下是这样的,不考虑自定义实现)
    • 我们的@Controller中通过@RequestMapping定义的REST服务,返回的是静态资源
    • 那么js,css,图片这些文件呢,在我们的web应用中并不会定义一个REST服务
    • 所以当接收一个http请求,找不到url关联映射时,默认场景下不认为这是一个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;
    }
    

    3. 小结

    本篇博文虽然也介绍了一种新的全局异常处理方式,实现效果和ControllerAdvice也差不多,但是并不推荐用这种方法, 原因如下

    • HandlerExceptionResolver的方式没有ControllerAdvice方式简介优雅
    • 官方提供的DefaultHandlerExceptionResolver已经非常强大了,基本上覆盖了http的各种状态码,我们自己再去定制的必要性不大

    II. 其他

    web系列博文

    项目源码

    1. 一灰灰Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

  • 相关阅读:
    HTML onblur 事件属性
    插入光标颜色 | caret-color (Basic User Interface) – CSS 中文开发手册
    gc (Runtime) – Python 中文开发手册
    《宾狗》
    《凭什么相信你,我的CNN模型?(篇二:万金油LIME)》
    《凭什么相信你,我的CNN模型?(篇一:CAM和Grad-CAM)》
    《如何利用CAM(类激活图)动态可视化模型的学习过程》
    《Attention最新进展》
    TCP-IP四书五经
    《统计学习方法》
  • 原文地址:https://www.cnblogs.com/yihuihui/p/11673496.html
Copyright © 2011-2022 走看看