Spring Boot 之 异常处理

July . 14 . 2019
  • 前言

这篇我来介绍一下spring boot 中常见的处理异常的三种方式, 话不多说直接开始吧。

  • 环境配置

JDK: 1.8

spring boot: v2.1.6.RELEASE

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.1.6.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.springboot</groupId>
     <artifactId>demo</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>demo</name>
     <description>Demo project for Spring Boot</description>
 
     <properties>
         <java.version>1.8</java.version>
     </properties>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 
 </project>
  • 准备一个测试的Demo

项目包结构如下图:

demo.JPG

MyController

package com.springboot.demo.controller;
 
 import com.springboot.demo.exception.MyException;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @RequestMapping("/my/")
 public class MyController {
 
     @GetMapping("exception")
     public String test() {
         throw new MyException(-1, "异常啦!");
 //        return "ok";
     }
 }

MyException

package com.springboot.demo.exception;
 
 public class MyException extends RuntimeException {
 
     private static final long serialVersionUID = -8733049534544489762L;
 
     private int status;
     private String msg;
 
     public MyException(int status, String msg) {
         this.status = status;
         this.msg = msg;
     }
 
     public String getMsg() {
         return msg;
     }
 
     public void setMsg(String msg) {
         this.msg = msg;
     }
 
 
     public int getStatus() {
         return status;
     }
 
     public void setStatus(int status) {
         this.status = status;
     }
 }

这样一个简单的测试Demo就写好了, 访问 http://localhost:8080/my/exception

效果如图

exception1.JPG

由于在代码中手动抛出了一个运行时异常, 所以会显示默认的异常页面, 下面我们就来对异常进行个性化的处理, 来返回我们想要的页面。

   1. 自定义错误页面

这是最简单的一种, 实现起来很简单, 只需要在资源文件夹底下新建 resources/error 目录, 并根据错误状态码来命名需要自定义的异常页面就可以了, 具体如下图:

创建文件夹

handle1.JPG

404.html

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>404</title>
 </head>
 <body>
     <h1>您所访问的页面不存在</h1>
 </body>
 </html>

500.html

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>500</title>
 </head>
 <body>
     <h1>服务器内部错误</h1>
 </body>
 </html>

运行效果如下

访问 http://localhost:8080/my/exception

500.JPG

访问 http://localhost:8080/my/exception1

404.JPG

这种方式虽然简单, 但是局限性很大, 对于接口模式开发的项目来说就不太实用了, 再来看下一种方式。


  2. 控制器通知

这种方式是基于 @ControllerAdvice 注解的控制器通知, 新建类ControllerExceptionHandler

ControllerExceptionHandler

package com.springboot.demo.controller;

import com.springboot.demo.exception.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice(basePackages = {"com.springboot.demo.controller.*"}, annotations = {RestController.class})
public class ControllerExceptionHandler {
Logger logger = LoggerFactory.getLogger(getClass());

@ExceptionHandler(MyException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map exceptionHandler(MyException ex) {

logger.info("异常处理器: " + this.getClass().getSimpleName());

Map res = new HashMap<>();
res.put("status", ex.getStatus());
res.put("message", ex.getMsg());
return res;
}
}
,>,>

basePackages 表示拦截的控制器所在的包, annotations 表示只拦截 RestController.class 注解的控制器, 启动项目来看看效果。

访问 http://localhost:8080/my/exception

handle2.JPG

日志也正常打印

log2.JPG

最后再看看扩展性更高的一种, HandlerExceptionResolver


3. HandlerExceptionResolver

这种实现方式也不难实现,  只需要继承 HandlerExceptionResolver 并实现对应方法即可, 代码如下:

新建 ExceptionResolver

ExceptionResolver

package com.springboot.demo.resolver;
 
 import com.springboot.demo.exception.MyException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.HandlerExceptionResolver;
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 @Component
 public class ExceptionResolver implements HandlerExceptionResolver {
 
     Logger logger = LoggerFactory.getLogger(getClass());
 
     @Override
     public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
 
         logger.info("异常处理器: " + this.getClass().getSimpleName());
 
         ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
 
         if (e instanceof MyException) {
             modelAndView.addObject("status", ((MyException) e).getStatus());
             modelAndView.addObject("msg", ((MyException) e).getMsg());
         }
 
         return modelAndView;
     }
 }

由于该接口方法需要返回一个 ModelAndView 对象, 所以你可以使用各种自定义的 ModelAndView 对象, 这里我使用 MappingJackson2JsonView 来返回一个 json 视图, 来看看效果。

访问 http://localhost:8080/my/exception

handle3.JPG

打印日志

log3.JPG

之所以说最后一种是扩展性高的一种方式, 是因为在这个方法内还可以拿到 HttpServletRequest 与 HttpServletResponse 对象, 可以进行一些个性化的操作, 这种也是本人最常用的一种方式。

end ~