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<String, Object> exceptionHandler(MyException ex) {

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

Map<String, Object> 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 ~