@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

@ControllerAdvice 和 @ExceptionHandler 的区别

  • ExceptionHandler, 方法注解, 作用于 Controller 级别. ExceptionHandler 注解为一个 Controler 定义一个异常处理器.
  • ControllerAdvice, 类注解, 作用于 整个 Spring 工程. ControllerAdvice 注解定义了一个全局的异常处理器.

需要注意的是, ExceptionHandler 的优先级比 ControllerAdvice 高, 即 Controller 抛出的异常如果既可以让 ExceptionHandler 标注的方法处理, 又可以让 ControllerAdvice 标注的类中的方法处理, 则优先让 ExceptionHandler 标注的方法处理.

处理 Controller 中的异常

为了方便地展示 Controller 异常处理的方式, 我创建了一个工程 SpringBootRESTfulErrorHandler, 其源码可以到我的 Github: github.com/yongshun 中找到.
SpringBootRESTfulErrorHandler 工程的目录结构如下:

首先我们定义了三个自定义的异常:

BaseException:


1

2

3

4

5

public class BaseException extends Exception {

    public BaseException(String message) {

        super(message);

    }

}

MyException1:


1

2

3

4

5

public class MyException1 extends BaseException {

    public MyException1(String message) {

        super(message);

    }

}

MyException2:


1

2

3

4

5

public class MyException2 extends BaseException {

    public MyException2(String message) {

        super(message);

    }

}

接着我们在 DemoController 中分别抛出这些异常:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

@RestController

public class DemoController {

    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");

    @RequestMapping("/ex1")

    public Object throwBaseException() throws Exception {

        throw new BaseException("This is BaseException.");

    }

    @RequestMapping("/ex2")

    public Object throwMyException1() throws Exception {

        throw new MyException1("This is MyException1.");

    }

    @RequestMapping("/ex3")

    public Object throwMyException2() throws Exception {

        throw new MyException2("This is MyException1.");

    }

    @RequestMapping("/ex4")

    public Object throwIOException() throws Exception {

        throw new IOException("This is IOException.");

    }

    @RequestMapping("/ex5")

    public Object throwNullPointerException() throws Exception {

        throw new NullPointerException("This is NullPointerException.");

    }

    @ExceptionHandler(NullPointerException.class)

    public String controllerExceptionHandler(HttpServletRequest req, Exception e) {

        logger.error("---ControllerException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());

        return e.getMessage();

    }

}

  • /ex1: 抛出 BaseException
  • /ex2: 抛出 MyException1
  • /ex3: 抛出 MyException2
  • /ex4: 抛出 IOException
  • /ex5: 抛出 NullPointerException

当 DemoController 抛出未捕获的异常时, 我们在 GlobalExceptionHandler 中进行捕获并处理:

GlobalExceptionHandler:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@RestController

@ControllerAdvice

public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");

    @ExceptionHandler(value = BaseException.class)

    @ResponseBody

    public Object baseErrorHandler(HttpServletRequest req, Exception e) throws Exception {

        logger.error("---BaseException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());

        return e.getMessage();

    }

    @ExceptionHandler(value = Exception.class)

    @ResponseBody

    public Object defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {

        logger.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());

        return e.getMessage();

    }

}

我们看到, GlobalExceptionHandler 类有两个注解:

  • RestController, 表明 GlobalExceptionHandler 是一个 RESTful Controller, 即它会以 RESTful 的形式返回回复.
  • ControllerAdvice, 表示 GlobalExceptionHandler 是一个全局的异常处理器.

在 GlobalExceptionHandler 中, 我们使用了 ExceptionHandler 注解标注了两个方法:

  • ExceptionHandler(value = BaseException.class): 表示 baseErrorHandler 处理 BaseException 异常和其子异常.
  • ExceptionHandler(value = Exception.class): 表示 defaultErrorHandler 会处理 Exception 异常和其所用子异常.

要注意的是, 和 try...catch 语句块, 异常处理的顺序也是从具体到一般, 即如果 baseErrorHandler 可以处理此异常, 则调用此方法来处理异常, 反之使用 defaultErrorHandler 来处理异常.

既然我们已经实现了 Controller 的异常处理, 那么接下来我们就来测试一下吧.
在浏览器中分别访问这些链接, 结果如下:

/ex1:

/ex2:

/ex3:

/ex4:

/ex5:

可以看到, /ex1, /ex2, /ex3 抛出的异常都由 GlobalExceptionHandler.baseErrorHandler 处理; /ex4 抛出的 IOException 异常由 GlobalExceptionHandler.defaultErrorHandler 处理. 但是 /ex5 抛出的 NullPointerException 异常为什么不是 defaultErrorHandler 处理, 而是由 controllerExceptionHandler 来处理呢? 回想到 @ControllerAdvice 和 @ExceptionHandler 的区别 这以小节中的内容时, 我们就知道原因了: 因为我们在 DemoController 中使用 ExceptionHandler 注解定义了一个 Controller 级的异常处理器, 这个级别的异常处理器的优先级比全局的异常处理器优先级高, 因此 Spring 发现 controllerExceptionHandler 可以处理 NullPointerException 异常时, 就调用这个方法, 而不会调用全局的 defaultErrorHandler 方法了.

处理 404 错误

Spring MVC

SpringBoot 默认提供了一个全局的 handler 来处理所有的 HTTP 错误, 并把它映射为 /error. 当发生一个 HTTP 错误, 例如 404 错误时, SpringBoot 内部的机制会将页面重定向到 /error 中.
例如下图中是一个默认的 SpringBoot 404 异常页面.

这个页面实在是太丑了, 我们能不能自定义一个异常页面呢? 当然可以了, 并且 SpringBoot 也给我们提示了: This application has no explicit mapping for /error, so you are seeing this as a fallback.
因此我们实现一个 /error 映射的 Controller 即可.


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

public class HttpErrorHandler implements ErrorController {

    private final static String ERROR_PATH = "/error";

    /**

     * Supports the HTML Error View

     *

     * @param request

     * @return

     */

    @RequestMapping(value = ERROR_PATH, produces = "text/html")

    public String errorHtml(HttpServletRequest request) {

        return "404";

    }

    /**

     * Supports other formats like JSON, XML

     *

     * @param request

     * @return

     */

    @RequestMapping(value = ERROR_PATH)

    @ResponseBody

    public Object error(HttpServletRequest request) {

        return "404";

    }

    /**

     * Returns the path of the error page.

     *

     * @return the error path

     */

    @Override

    public String getErrorPath() {

        return ERROR_PATH;

    }

}

根据上面代码我们看到, 为了实现自定义的 404 页面, 我们实现了 ErrorController 接口:


1

2

3

public interface ErrorController {

    String getErrorPath();

}

这个接口只有一个方法, 当出现 HTTP 错误时, SpringBoot 会将页面重定向到 getErrorPath 方法返回的页面中. 这样我们就可以实现自定义的错误页面了.

RESTful API

提供一个自定义的 "/error" 页面对 Spring MVC 的服务来说自然是没问题的, 但是如果我们的服务是一个 RESTful 服务的话, 这样做就不行了.
当用户调用了一个不存在的 RESTful API 时, 我们想记录下这个异常访问, 并返回一个代表错误的 JSON 给客户端, 这该怎么实现呢?
我们很自然地想到, 我们可以使用处理异常的那一套来处理 404 错误码.
那么我们来试一下这个想法是否可行吧.

奇怪的是, 当我们在浏览器中随意输入一个路径时, 代码并没有执行到异常处理逻辑中, 而是返回了一个 HTML 页面给我们, 这又是怎么回事呢?

原来 Spring Boot 中, 当用户访问了一个不存在的链接时, Spring 默认会将页面重定向到 **/error** 上, 而不会抛出异常.

既然如此, 那我们就告诉 Spring Boot, 当出现 404 错误时, 抛出一个异常即可. 在 application.properties 中添加两个配置:


1

2

spring.mvc.throw-exception-if-no-handler-found=true

spring.resources.add-mappings=false

上面的配置中, 第一个 spring.mvc.throw-exception-if-no-handler-found 告诉 SpringBoot 当出现 404 错误时, 直接抛出异常. 第二个 spring.resources.add-mappings 告诉 SpringBoot 不要为我们工程中的资源文件建立映射. 这两个配置正是 RESTful 服务所需要的.
当加上这两个配置后, 我们再来试一下:

可以看到, 现在确实是在 defaultErrorHandler 中处理了.

参考

https://www.cnblogs.com/Zombie-Xian/p/6251189.html

https://blog.csdn.net/smithallenyu/article/details/72331387
https://blog.csdn.net/kinginblue/article/details/70186586
https://blog.csdn.net/qq_17586821/article/details/79831244
https://www.cnblogs.com/shanheyongmu/p/5872442.html

原文地址:https://www.cnblogs.com/xd502djj/p/9873172.html

时间: 2024-10-10 05:15:51

@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常的相关文章

【统一异常处理】@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

1.利用springmvc注解对Controller层异常全局处理 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚. 如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端.但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护

@ControllerAdvice + @ExceptionHandler全局处理Controller层异常(转)

0.前言 对于与数据库相关的Spring MVC项目,我们通常会把事务配置在 Service层,当数据库操作失败时让Service层抛出运行时异常,Spring事物管理器就会进行回滚. 如此一来,我们的Controller层就不得不进行try-catch Service层的异常,否则会返回一些不友好的错误信息到客户端.但是,Controller层每个方法体都写一些模板化的try-catch的代码,很难看也难维护,特别是还需要对Service层的不同异常进行不同处理的时候.例如以下 Control

Junit mockito 测试Controller层方法有Pageable异常

1.问题 在使用MockMVC+Mockito模拟Service层返回的时候,当我们在Controller层中参数方法调用有Pageable对象的时候,我们会发现,我们没办法生成一个Pageable的对象,会报一个Pageable是一个接口的错误.当我们把所有的参数从Pageable接口变成Pageable的实现类PageRequest的时候,所有的方法参数都换成PageRequest,又会出现一个新的错误,且不说PageRequest不能作为参数用于hibernate的分页查询,另一方面,它没

Controller 层实现

一.实验介绍 1.1 实验内容 本节课程主要利用 Spring MVC 框架实现 Controller 层以及一些辅助类的实现. 1.2 实验知识点 Spring MVC 框架 1.3 实验环境 JDK1.8 Eclipse JavaEE 二.实验步骤 在项目 hrms 的目录 src/main/java 下新建包 com.shiyanlou.controller,作为 Controller 层的包,新建包 com.shiyanlou.util,作为辅助类的包,这些辅助类是为了使 Control

添加仓储分类列表接口(service层和controller层)

一.负责顶级分类组请求处理的类: controller service serviceImpl 二.负责次级分类请求处理的类: controller service serviceImpl 三.负责物品总计请求处理的类 controller service serviceImpl 四.负责日志记录请求处理的类 controller service servceImpl 五.添加全局异常处理controller并简易处理异常 六.在yml配置文件配置json解析日期对象时的日期格式 原文地址:ht

DAO层,Service层,Controller层、View层介绍

来自:http://jonsion.javaeye.com/blog/592335 DAO层 DAO 层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此 接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及 有关数据库连接的参数都在Spring的配置文件中进行配置. Service层 Service 层主要负责业

Controller层的写法

项目中的两个Controller层实现类,一个是跳转到jsp页面,一个是以Json形式返回Map键值对. 跳转到jsp页面: 1 package com.controller; 2 3 import java.io.IOException; 4 5 import javax.servlet.http.HttpServletRequest; 6 7 import org.apache.commons.httpclient.HttpClient; 8 import org.apache.common

DAO层,Service层,Controller层、View层

DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置. Service层:Service层主要负责业务模块的逻辑应用设计.同样是首先设计接口,再设计其实现的类,接着再Spring的配置文件中配

Spring mvc 对象的对象的属性如何传到controller层

jsp页面的form: <form class="am-form" id="addForm" action="addStudent" method="post"> <div class="am-g am-margin-top"> <div class="am-u-sm-4 am-u-md-2 am-text-right">学生编号</div>