spring boot @ControllerAdvice 拦截异常并统一处理

1:@ControllerAdvice:全局捕获异常,异常集中处理,更好的使业务逻辑与异常处理剥离开

把@ControllerAdvice定义在一个类上,该类则负责捕获所有@RequestMapping上发生的异常(包括controller调用的service)
2:@ExceptionHandler(value = Exception.class):统一处理某一类异常

把@ExceptionHandler(value = Exception.class)定义在一个方法上,声明该方法用于捕获value所指的类型的异常(注意:当该异常的子父类都被声明时,按照先子后父的顺序进行捕获)
在方法中捕获到异常进行处理后,即可重定向到一个视图,也可返回一个json;此时需要@ResponseBody。
3:@ResponseBody

   和@ExceptionHandler一同用在方法上,声明在方法中捕获到异常并进行处理后,返回的数据类型不是html页面,而是某种格式的数据。(@ResponseBody的本质作用)
 4:@ResponseStatus:将某种异常映射为HTTP状态码,可用在方法上,也可以用在类上(自定义运行时异常类)。

当作用到异常类上时,实际上就是将该异常类的对象转换成某个HTTP状态码,然后以该状态码去通知客户端。(本文不再讨论)
当作用到方法上时,实际上就是改变方法返回值的HTTP状态码,然后返回给客户端。
例子如下:

补充:上图例子虽然是对GunsException异常的捕获,并在方法中进行了处理,最终在方法返回时,如果没有加上@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR),则该方法返回给客户端的状态码是200(即正常状态码),如果是ajax发送的请求,则会回调success()方法。但是加上@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)之后,该方法返回给客户端的状态码就变了。所以:@ResponseStatus如果加在方法上,就是改变方法返回值的HTTP状态码。
 

在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。参考:@ControllerAdvice 文档

一、介绍

创建 MyControllerAdvice,并添加 @ControllerAdvice注解。

package com.sam.demo.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

/**
 * controller 增强器
 * @author sam
 * @since 2017/7/17
 */
@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
     * @param binder
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {}

    /**
     * 把值绑定到Model中,使全局@RequestMapping可以获取到该值
     * @param model
     */
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("author", "Magical Sam");
    }

    /**
     * 全局异常捕捉处理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        map.put("code", 100);
        map.put("msg", ex.getMessage());
        return map;
    }

}

启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。

@ModelAttribute:在Model上设置的值,对于所有被 @RequestMapping 注解的方法中,都可以通过 ModelMap 获取,如下:

@RequestMapping("/home")
public String home(ModelMap modelMap) {
    System.out.println(modelMap.get("author"));
}

//或者 通过@ModelAttribute获取

@RequestMapping("/home")
public String home(@ModelAttribute("author") String author) {
    System.out.println(author);
}

@ExceptionHandler 拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型,上面拦截了 Exception.class 这种异常。

二、自定义异常处理(全局异常处理)

spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,下面自定义异常处理,提供友好展示。

1、编写自定义异常类:

package com.sam.demo.custom;

/**
 * @author sam
 * @since 2017/7/17
 */
public class MyException extends RuntimeException {

    public MyException(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private String code;
    private String msg;

    // getter & setter
}

注:spring 对于 RuntimeException 异常才会进行事务回滚。

2、编写全局异常处理类

创建 MyControllerAdvice.java,如下:

package com.sam.demo.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * controller 增强器
 *
 * @author sam
 * @since 2017/7/17
 */
@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 全局异常捕捉处理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        map.put("code", 100);
        map.put("msg", ex.getMessage());
        return map;
    }
    
    /**
     * 拦截捕捉自定义异常 MyException.class
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MyException.class)
    public Map myErrorHandler(MyException ex) {
        Map map = new HashMap();
        map.put("code", ex.getCode());
        map.put("msg", ex.getMsg());
        return map;
    }

}

3、controller中抛出异常进行测试。

@RequestMapping("/home")
public String home() throws Exception {

//        throw new Exception("Sam 错误");
    throw new MyException("101", "Sam 错误");

}

启动应用,访问:http://localhost:8080/home ,正常显示以下json内容,证明自定义异常已经成功被拦截。

{"msg":"Sam 错误","code":"101"}

* 如果不需要返回json数据,而要渲染某个页面模板返回给浏览器,那么MyControllerAdvice中可以这么实现:

@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandler(MyException ex) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error");
    modelAndView.addObject("code", ex.getCode());
    modelAndView.addObject("msg", ex.getMsg());
    return modelAndView;
}

在 templates 目录下,添加 error.ftl(这里使用freemarker) 进行渲染:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误页面</title>
</head>
<body>
    <h1>${code}</h1>
    <h1>${msg}</h1>
</body>
</html>

重启应用,http://localhost:8080/home 显示自定的错误页面内容。

补充:如果全部异常处理返回json,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice


项目中的真实异常处理类:

package com.jxwy.exception;


import com.jxwy.json.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author ningpengtao
 * @email ning.pengtao@trs.com.cn
 * <pre>
 * 	通用异常处理
 * </pre>
 */
@ControllerAdvice
@ResponseBody
@Slf4j
public class CommonExceptionAdvice {
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MissingServletRequestParameterException.class)
  public AjaxResult<String,Object> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
    log.error("缺少请求参数", e);
    return new AjaxResult<String,Object>().failure("required_parameter_is_not_present");
  }

  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(HttpMessageNotReadableException.class)
  public AjaxResult<String,Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    log.error("参数解析失败", e);
    return new AjaxResult<String,Object>().failure("could_not_read_json");
  }

  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public AjaxResult<Object,Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    log.error("参数验证失败", e);
    BindingResult result = e.getBindingResult();
    FieldError error = result.getFieldError();
    String field = error.getField();
    String code = error.getDefaultMessage();
    
    Map<String, String> errorMap = new HashMap<>();
    
    errorMap.put(field, code);

    return new AjaxResult<Object,Object>().failure(errorMap);
  }

  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(BindException.class)
  public AjaxResult<Object,Object> handleBindException(BindException e) {
    log.error("参数绑定失败", e);
    BindingResult result = e.getBindingResult();
    FieldError error = result.getFieldError();
    String field = error.getField();
    String code = error.getDefaultMessage();
    
    Map<String, String> errorMap = new HashMap<>();
    
    errorMap.put(field, code);

    return new AjaxResult<Object,Object>().failure(errorMap);
  }

  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ConstraintViolationException.class)
  public AjaxResult<String,Object> handleServiceException(ConstraintViolationException e) {
    log.error("参数验证失败", e);
    Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
    ConstraintViolation<?> violation = violations.iterator().next();
    String message = violation.getMessage();

    return new AjaxResult<String,Object>().failure(message);
  }

  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ValidationException.class)
  public AjaxResult<String,Object> handleValidationException(ValidationException e) {
    log.error("参数验证失败", e);
    return new AjaxResult<String,Object>().failure("validation_exception");
  }

  /**
   * 405 - Method Not Allowed
   */
  @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  public AjaxResult<String,Object> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
    log.error("不支持当前请求方法", e);
    return new AjaxResult<String,Object>().failure("request_method_not_supported");
  }

  /**
   * 415 - Unsupported Media Type
   */
  @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  public AjaxResult<String,Object> handleHttpMediaTypeNotSupportedException(Exception e) {
    log.error("不支持当前媒体类型", e);
    return new AjaxResult<String,Object>().failure("content_type_not_supported");
  }

  /**
   * 500 - Internal Server Error
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(ServiceException.class)
  public AjaxResult<String,Object> handleServiceException(ServiceException e) {
    log.error("业务逻辑异常", e);
    return new AjaxResult<String,Object>().failure("业务逻辑异常:" + e.getMessage());
  }

  /**
   * 500 - Internal Server Error
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(Exception.class)
  public AjaxResult<String,Object> handleException(Exception e) {
    log.error("通用异常", e);
    return new AjaxResult<String,Object>().failure(e.getMessage());
  }

  /**
   * 操作数据库出现异常:名称重复,外键关联
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(DataIntegrityViolationException.class)
  public AjaxResult<String,Object> handleException(DataIntegrityViolationException e) {
    log.error("操作数据库出现异常:", e);
    return new AjaxResult<String,Object>().failure("操作数据库出现异常:字段重复、有外键关联等");
  }
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值