SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

24次阅读

共计 5632 个字符,预计需要花费 15 分钟才能阅读完成。

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

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

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

<!– more –>

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

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 系列博文

  • 191010-SpringBoot 系列教程 web 篇之全局异常处理
  • 190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置
  • 190929-SpringBoot 系列教程 web 篇之重定向
  • 190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势
  • 190905-SpringBoot 系列教程 web 篇之中文乱码问题解决
  • 190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器
  • 190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总
  • 190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总
  • 190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建
  • 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建
  • 190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建
  • 190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明
  • 190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析
  • 190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建
  • 190316-Spring MVC 之基于 xml 配置的 web 应用构建
  • 190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid

项目源码

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 项目:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/209-web-error

1. 一灰灰 Blog

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

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

  • 一灰灰 Blog 个人博客 https://blog.hhui.top
  • 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top

正文完
 0