Spring Boot系列十 Spring MVC全局异常处理总结

来源:互联网 发布:淘宝发物流怎么填单号 编辑:程序博客网 时间:2024/06/16 13:00

1. 概述

SpringMVC 提供的异常处理主要有两种方式:

  • 一种是直接实现自己的HandlerExceptionResolver
  • 一种是使用注解

通过注解的方式实现处理异常主要有以下两种方式:

  • 1 @ControllerAdvice+@ExceptionHandler:配置对全局异常进行处理
  • 2 @Controller + @ExceptionHandler:配置对当前所在Controller的异常进行处理

在SpringMVC中,处理异常类实际上是HandlerExceptionResolver子类。HandlerExceptionResolver处理所有controller类在执行过程中抛出的未被处理的异常。

本文演示如何使用以上多种处理异常的方式,最后演示以不同的方式将异常结果返回给调用者

  • 返回 modelAndView
  • 返回一个页面的地址
  • 返回 JSON
  • 返回 http 错误码

2. 演示工程mvc

所有代码都中mvc工程中

3.1. 基础类和JSP页面

辅助类
EmployeeEx:POJO类
ExceptionJSONInfo:封装返回的JSON对象

public class EmployeeEx {    private String name;    private int id;    …}public class ExceptionJSONInfo {    private String url;    private String message;    …}

定义测试的异常类
EmployeeExNotFoundException

public class EmployeeExNotFoundException extends Exception {  private static final long serialVersionUID = -3332292346834265371L;  public EmployeeExNotFoundException(int id){    super("EmployeeNotFoundException with id="+id);  }}

EmployeeExJsonException

public class EmployeeExJsonException extends Exception {  private static final long serialVersionUID = -3332292346834265371L;  public EmployeeExJsonException(int id){    super("EmployeeExJsonException with id="+id);  }}

JSP页面
本demo使用的jsp都在此“META-INF.resources.WEB-INF.page.exceptionhandling”目录下,由于代码简单,这里一一列出。

3. 通过注解的方式捕获异常

本节演示两种通过注解捕获异常的方式
- @Controller + @ExceptionHandler
- @ControllerAdvice + @ExceptionHandler

3.1. @Controller + @ExceptionHandler

@Controller:注解此类是Controller类
@ExceptionHandler:此注解注解到类的方法上,当此注解里定义的异常抛出时,此方法会被执行。如果@ExceptionHandler所在的类是@Controller,则此方法只作用在此类。如果@ExceptionHandler所在的类是@ControllerAdvice,则此方法会作用在全局。本节演示前种用法

下面演示这种用法:

EmployeeExController:

  • 在@Controller里定义的@ExceptionHandler只截获所在类抛出的异常
  • handleEmployeeNotFoundException()通过@ExceptionHandler定义要捕获的异常是EmployeeExNotFoundException,此方法会转到exceptionhandling/error页面,此页面会打印错误信息
@Controllerpublic class EmployeeExController {  private static final Logger logger = LoggerFactory.getLogger(EmployeeExController.class);  @RequestMapping(value="/emp/{id}", method=RequestMethod.GET)  public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{    if(id==1){      throw new EmployeeExNotFoundException(id);    }else if(id==2){    …  }  /**   * 此方法只处理本类抛出的 EmployeeNotFoundException 异常   * @param request   * @param ex   * @return   */  @ExceptionHandler(EmployeeExNotFoundException.class)  public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){    logger.error("Requested URL="+request.getRequestURL());    logger.error("Exception Raised="+ex);    ModelAndView modelAndView = new ModelAndView();      modelAndView.addObject("exception", ex);      modelAndView.addObject("url", request.getRequestURL());     // 转到error.jsp      modelAndView.setViewName("exceptionhandling/error");      return modelAndView;  } }

测试
执行URL:
http://127.0.0.1:8080/emp/1
输出
这里写图片描述

3.2. @ControllerAdvice + @ExceptionHandler

@ControllerAdvice:定义全局异常处理的类,默认情况下它会监控所有的@RequestMapping方法抛出的异常。不过我们也可以对指定过滤的条件

EmployeeExController
此方法中抛出SQLException

@Controllerpublic class EmployeeExController { @RequestMapping(value="/emp/{id}", method=RequestMethod.GET)  public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{    if(id==1){      ….    }else if(id==2){      throw new SQLException("SQLException, id="+id);    }else if(id==3){      …      }

GlobalExceptionHandler
@ControllerAdvice:通过@ExceptionHandler方法捕获特定异常。默认情况下@ControllerAdvice监控所有的@RequestMapping方法,也可以对指定过滤的条件。下面的方法会捕获所有抛出SQLException的@RequestMapping方法

/** * 全局异常处理异常 *  @ControllerAdvice * */@ControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(SQLException.class)    public String handleSQLException(HttpServletRequest request, Exception ex) {        logger.info("SQLException Occured:: URL=" + request.getRequestURL());        return "exceptionhandling/database_error";    }    ….}

@ControllerAdvice默认监控所有的@RequestMapping方法,也可以对指定过滤的条件:

// 监控所有的被@RestController注解的Controllers类 @ControllerAdvice(annotations = RestController.class)public class AnnotationAdvice {}// 监控特定的包下的Controllers类@ControllerAdvice("org.example.controllers")public class BasePackageAdvice {}// 监控指定类的Controllers类@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})public class AssignableTypesAdvice {}

测试
执行URL:
http://127.0.0.1:8080/emp/2
输出
这里写图片描述

4. 通过HandlerExceptionResolver的方式捕获

在接口HandlerExceptionResolver,我们可以自定义实现全局异常捕获

4.1. 系统默认实现的HandlerExceptionResolver

以下是系统默认加载到spring mvc容器中的HandlerExceptionResolver

  • ExceptionHandlerExceptionResolver: 根据@ExceptionHandler注解的方法处理对应的异常。其实上文的通过注解的方式处理异常,实际就是在这个类中实现
  • ResponseStatusExceptionResolver: 根据@ResponseStatus注解的方法处理异常
  • DefaultHandlerExceptionResolver: 将异常转化为特定的HTTP的状态码
  • HandlerExceptionResolverComposite:此类通过列表包含以上3个HandlerExceptionResolver,当捕获异常时,会循环调用以上3个HandlerExceptionResolver进行处理

4.2. 实现自己的HandlerExceptionResolver

此类只演示HandlerExceptionResolver的用法,只打印错误信息,不进行任何处理。通过接口Ordered的getOrder定义此HandlerExceptionResolver的优先级,优先级最高

@Component // 需要带上此注解public class MySimpleHandlerExceptionResolverimplements HandlerExceptionResolver,Ordered{    private static final Logger logger = LoggerFactory.getLogger(MySimpleMappingExceptionResolver.class);    @Override    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,            Exception ex) {        logger.info("url = {}, exception message = {}", request.getRequestURI(), ex.getMessage());        // 返回null,让后面HandlerExceptionResolver继续进行处理;如果不让后面的HandlerExceptionResolver进行处理,则这里返回一个ModelAndView对象即可        return null;    }    public int getOrder(){        // 表示此HandlerExceptionResolver的优先级最高        return Ordered.HIGHEST_PRECEDENCE;    }}

4.3. 测试

由于此andlerExceptionResolver的优先级最高,所以所有抛出异常被会被此类捕获,如:请求 http://127.0.0.1:8080/emp/2
后台有打印信息,说明此配置生效了

2017-12-20 11:39:03.145 [http-nio-8080-exec-1] INFO  c.h.s.m.e.MySimpleHandlerExceptionResolver - url = /emp/2, exception message = SQLException, id=2

5. 对异常进行处理

捕获异常后,我们可以返回所有的spring mvc能够返回的所有的类型,这里只演示以下几种:

  • 返回 modelAndView
  • 返回一个页面的地址
  • 返回 JSON
  • 返回 http 错误码

5.1. 返回 modelAndView

上面的 “@Controller + @ExceptionHandler” 演示这个方式的用法

5.2. 返回一个页面的地址

上面的 “@ControllerAdvice + @ExceptionHandler” 演示这个方式的用法

5.3. 返回 JSON

EmployeeExController

@Controllerpublic class EmployeeExController {    @RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)    public String getEmployee(@PathVariable("id") int id, Model model) throws Exception {        ..        } else if (id == 4) {            throw new EmployeeExJsonException(id);        } else if (id == 10) {            …}

GlobalExceptionHandler
@ResponseBody:截获异常,生成对象并通过此注解生成json,返回给客户端

@ControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(EmployeeExJsonException.class)    public @ResponseBody ExceptionJSONInfo handleEmployeeNotFoundException(HttpServletRequest request, Exception ex) {       ExceptionJSONInfo response = new ExceptionJSONInfo();       response.setUrl(request.getRequestURL().toString());       response.setMessage(ex.getMessage());       return response;    }}

测试
执行 http://127.0.0.1:8080/emp/4
返回
这里写图片描述

5.4. 返回 http 错误码

如果发生IOException异常,则作为返回http错误代码为404。

EmployeeExController

@Controllerpublic class EmployeeExController {  @RequestMapping(value="/emp/{id}", method=RequestMethod.GET)  public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{    …     }else if(id==3){      throw new IOException("IOException, id="+id);    }else if(id==10){      …      }}

GlobalExceptionHandler :
如果发生IOException异常,则作为返回http错误代码为404
这里写代码片

@ControllerAdvicepublic class GlobalExceptionHandler {    /**     * For IOException, we are returning void with status code as 404,      * so our error-page will be used in this case.     *      */    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "IOException occured")    @ExceptionHandler(IOException.class)    public void handleIOException() {        logger.error("IOException handler executed");        // returning 404 error code    }}

测试
执行 http://127.0.0.1:8080/emp/3

6. 代码

上文的详细代码见github代码,请尽量使用tag v0.5,不要使用master,因为master一直在变,不能保证文章中代码和github上的代码一直相同

原创粉丝点击