美文网首页JAVA随笔码农日历
SpringBoot系列教程web篇之全局异常处理

SpringBoot系列教程web篇之全局异常处理

作者: 一灰灰blog | 来源:发表于2019-10-11 18:38 被阅读0次

    当我们的后端应用出现异常时,通常会将异常状况包装之后再返回给调用方或者前端,在实际的项目中,不可能对每一个地方都做好异常处理,再优雅的代码也可能抛出异常,那么在 Spring 项目中,可以怎样优雅的处理这些异常呢?

    本文将介绍一种全局异常处理方式,主要包括以下知识点

    • @ControllerAdvice Controller 增强
    • @ExceptionHandler 异常捕获
    • @ResponseStatus 返回状态码
    • NoHandlerFoundException 处理(404 异常捕获)

    右键查看原文: SpringBoot系列教程web篇之全局异常处理

    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>
    

    依然是一般的流程,pom 依赖搞定之后,写一个程序入口

    /**
     * Created by @author yihui in 15:26 19/9/13.
     */
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class);
        }
    }
    

    II. 异常处理

    1. @ControllerAdvice

    我们通常利用@ControllerAdvice配合注解@ExceptionHandler来实现全局异常捕获处理

    • @ControllerAdvice为所有的 Controller 织入增强方法
    • @ExceptionHandler标记在方法上,表示当出现对应的异常抛出到上层时(即没有被业务捕获),这个方法会被触发

    下面我们通过实例进行功能演示

    a. 异常捕获

    我们定义两个异常捕获的 case,一个是除 0,一个是数组越界异常

    @Slf4j
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        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;
        }
    
        @ResponseBody
        @ExceptionHandler(value = ArithmeticException.class)
        public String handleArithmetic(HttpServletRequest request, HttpServletResponse response, ArithmeticException e)
                throws IOException {
            log.info("divide error!");
            return "divide 0: " + getThrowableStackInfo(e);
        }
    
        @ResponseBody
        @ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
        public String handleArrayIndexOutBounds(HttpServletRequest request, HttpServletResponse response,
                ArrayIndexOutOfBoundsException e) throws IOException {
            log.info("array index out error!");
            return "aryIndexOutOfBounds: " + getThrowableStackInfo(e);
        }
    }
    

    在上面的测试中,我们将异常堆栈返回调用方

    b. 示例服务

    增加几个测试方法

    @Controller
    @RequestMapping(path = "page")
    public class ErrorPageRest {
    
        @ResponseBody
        @GetMapping(path = "divide")
        public int divide(int sub) {
            return 1000 / sub;
        }
    
        private int[] ans = new int[]{1, 2, 3, 4};
    
        @ResponseBody
        @GetMapping(path = "ary")
        public int ary(int index) {
            return ans[index];
        }
    }
    

    c. 测试说明

    实例测试如下,上面我们声明捕获的两种异常被拦截并输出对应的堆栈信息;

    但是需要注意

    • 404 和未捕获的 500 异常则显示的 SpringBoot 默认的错误页面;
    • 此外我们捕获返回的 http 状态码是 200
    image

    2. @ResponseStatus

    上面的 case 中捕获的异常返回的状态码是 200,但是在某些 case 中,可能更希望返回更合适的 http 状态码,此时可以使用ResponseStatus来指定

    使用方式比较简单,加一个注解即可

    @ResponseBody
    @ExceptionHandler(value = ArithmeticException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleArithmetic(HttpServletRequest request, HttpServletResponse response, ArithmeticException e)
            throws IOException {
        log.info("divide error!");
        return "divide 0: " + getThrowableStackInfo(e);
    }
    
    image

    3. 404 处理

    通过@ControllerAdvice配合@ExceptionHandler可以拦截 500 异常,如果我希望 404 异常也可以拦截,可以如何处理?

    首先修改配置文件application.properties,将NoHandlerFoundException抛出来

    # 出现错误时, 直接抛出异常
    spring.mvc.throw-exception-if-no-handler-found=true
    # 设置静态资源映射访问路径,下面两个二选一,
    spring.mvc.static-path-pattern=/statics/**
    # spring.resources.add-mappings=false
    

    其次是定义异常捕获

    @ResponseBody
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleNoHandlerError(NoHandlerFoundException e, HttpServletResponse response) {
        return "noHandlerFound: " + getThrowableStackInfo(e);
    }
    

    再次测试如下,404 被我们捕获并返回堆栈信息

    image

    II. 其他

    0. 项目

    web 系列博文

    项目源码

    1. 一灰灰 Blog

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

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

    一灰灰blog

    相关文章

      网友评论

        本文标题:SpringBoot系列教程web篇之全局异常处理

        本文链接:https://www.haomeiwen.com/subject/usyxmctx.html