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

简介: 关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理;本篇博文则带来另外一种并不常见的使用方式,通过实现自定义的HandlerExceptionResolver,来处理异常状态

关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现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("自定义异常处理!!! \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异常的实测情况


image.png


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的各种状态码,我们自己再去定制的必要性不大





相关文章
|
1月前
|
JSON 前端开发 Java
【十八】springboot实现自定义全局异常处理
【十八】springboot实现自定义全局异常处理
34 1
【十八】springboot实现自定义全局异常处理
|
4天前
|
JSON Java fastjson
Spring Boot 底层级探索系列 04 - Web 开发(2)
Spring Boot 底层级探索系列 04 - Web 开发(2)
15 0
|
30天前
|
API
2024常用Web支付开发讲解教程
本教程为web支付开发,讲解了最常用的两钟支付:支付宝支付和微信支付,服务器配置和API对接,学完本课程可以学会微信支付、和支付宝支付开发。
18 2
2024常用Web支付开发讲解教程
|
30天前
|
架构师 前端开发
web全栈架构师第16期教程
互联网时代已进入后半场,行业环境发生了显著变化。互联网人,尤其是技术人员,如何在加速更迭的技术浪潮中持续充电,提升自身价值,是当下必须面对的挑战。课程涉及了现下前端实际开发时所需要的各块内容,并深度对标 阿里 P6+级别所具备的知识储备及开发技能,奠定源码阅读基础和全栈开发能力。
19 3
web全栈架构师第16期教程
|
1月前
|
XML JavaScript 前端开发
Web 扫描神器:WhatWeb 保姆级教程(附链接)
Web 扫描神器:WhatWeb 保姆级教程(附链接)
52 0
|
1月前
|
存储 网络协议 安全
Web 扫描神器:Gobuster 保姆级教程(附链接)
Web 扫描神器:Gobuster 保姆级教程(附链接)
169 0
|
1月前
|
存储 搜索推荐 Java
springboot280基于WEB的旅游推荐系统设计与实现
springboot280基于WEB的旅游推荐系统设计与实现
|
1月前
|
搜索推荐 Java 数据库
springboot基于Web的社区医院管理服务系统
springboot基于Web的社区医院管理服务系统
|
2月前
|
JSON IDE Java
创建一个简单的Spring Boot Web项目
创建一个简单的Spring Boot Web项目
52 1
|
19天前
|
监控 JavaScript 前端开发
《理解 WebSocket:Java Web 开发的实时通信技术》
【4月更文挑战第4天】WebSocket是Java Web实时通信的关键技术,提供双向持久连接,实现低延迟、高效率的实时交互。适用于聊天应用、在线游戏、数据监控和即时通知。开发涉及服务器端实现、客户端连接及数据协议定义,注意安全、错误处理、性能和兼容性。随着实时应用需求增加,WebSocket在Java Web开发中的地位将更加重要。