Springboot自定义异常处理

背景

Springboot 默认把异常的处理集中到一个ModelAndView中了,但项目的实际过程中,这样做,并不能满足我们的要求。具体的自定义异常的处理,参看以下

前提

  1. Springboot 默认的application properties
  2. Spring Boot异常处理详解

    具体实现

    如果仔细看完Spring boot的异常处理详解,并且研究过源码后,我觉得具体的实现可以不用看了。。。
    重写定义错误页面的url,默认只有一个/error
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(){
    return new EmbeddedServletContainerCustomizer(){
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
    container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
    container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
    container.addErrorPages(new ErrorPage(java.lang.Throwable.class,"/error/500"));
    }
    };
    }
    重写通过实现ErrorController,重写BasicErrorController的功能实现
    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    **
    * 重写BasicErrorController,主要负责系统的异常页面的处理以及错误信息的显示
    * @see org.springframework.boot.autoconfigure.web.BasicErrorController
    * @see org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
    *
    * @author Jonathan
    * @version 2016/5/31 11:22
    * @since JDK 7.0+
    */
    @Controller
    @RequestMapping(value = "error")
    @EnableConfigurationProperties({ServerProperties.class})
    public class ExceptionController implements ErrorController {

    private ErrorAttributes errorAttributes;

    @Autowired
    private ServerProperties serverProperties;


    /**
    * 初始化ExceptionController
    * @param errorAttributes
    */
    @Autowired
    public ExceptionController(ErrorAttributes errorAttributes) {
    Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
    this.errorAttributes = errorAttributes;
    }


    /**
    * 定义404的ModelAndView
    * @param request
    * @param response
    * @return
    */
    @RequestMapping(produces = "text/html",value = "404")
    public ModelAndView errorHtml404(HttpServletRequest request,
    HttpServletResponse response) {
    response.setStatus(getStatus(request).value());
    Map<String, Object> model = getErrorAttributes(request,
    isIncludeStackTrace(request, MediaType.TEXT_HTML));
    return new ModelAndView("error/404", model);
    }

    /**
    * 定义404的JSON数据
    * @param request
    * @return
    */
    @RequestMapping(value = "404")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error404(HttpServletRequest request) {
    Map<String, Object> body = getErrorAttributes(request,
    isIncludeStackTrace(request, MediaType.TEXT_HTML));
    HttpStatus status = getStatus(request);
    return new ResponseEntity<Map<String, Object>>(body, status);
    }

    /**
    * 定义500的ModelAndView
    * @param request
    * @param response
    * @return
    */
    @RequestMapping(produces = "text/html",value = "500")
    public ModelAndView errorHtml500(HttpServletRequest request,
    HttpServletResponse response) {
    response.setStatus(getStatus(request).value());
    Map<String, Object> model = getErrorAttributes(request,
    isIncludeStackTrace(request, MediaType.TEXT_HTML));
    return new ModelAndView("error/500", model);
    }


    /**
    * 定义500的错误JSON信息
    * @param request
    * @return
    */
    @RequestMapping(value = "500")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) {
    Map<String, Object> body = getErrorAttributes(request,
    isIncludeStackTrace(request, MediaType.TEXT_HTML));
    HttpStatus status = getStatus(request);
    return new ResponseEntity<Map<String, Object>>(body, status);
    }


    /**
    * Determine if the stacktrace attribute should be included.
    * @param request the source request
    * @param produces the media type produced (or {@code MediaType.ALL})
    * @return if the stacktrace attribute should be included
    */
    protected boolean isIncludeStackTrace(HttpServletRequest request,
    MediaType produces) {
    ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
    if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
    return true;
    }
    if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
    return getTraceParameter(request);
    }
    return false;
    }


    /**
    * 获取错误的信息
    * @param request
    * @param includeStackTrace
    * @return
    */
    private Map<String, Object> getErrorAttributes(HttpServletRequest request,
    boolean includeStackTrace) {
    RequestAttributes requestAttributes = new ServletRequestAttributes(request);
    return this.errorAttributes.getErrorAttributes(requestAttributes,
    includeStackTrace);
    }

    /**
    * 是否包含trace
    * @param request
    * @return
    */
    private boolean getTraceParameter(HttpServletRequest request) {
    String parameter = request.getParameter("trace");
    if (parameter == null) {
    return false;
    }
    return !"false".equals(parameter.toLowerCase());
    }

    /**
    * 获取错误编码
    * @param request
    * @return
    */
    private HttpStatus getStatus(HttpServletRequest request) {
    Integer statusCode = (Integer) request
    .getAttribute("javax.servlet.error.status_code");
    if (statusCode == null) {
    return HttpStatus.INTERNAL_SERVER_ERROR;
    }
    try {
    return HttpStatus.valueOf(statusCode);
    }
    catch (Exception ex) {
    return HttpStatus.INTERNAL_SERVER_ERROR;
    }
    }

    /**
    * 实现错误路径,暂时无用
    * @see ExceptionMvcAutoConfiguration#containerCustomizer()
    * @return
    */
    @Override
    public String getErrorPath() {
    return "";
    }

    }

总结

第一步,通过定义containerCustomizer,重写定义了异常处理对应的视图。目前定义了404和500,可以继续扩展。
第二步,重写BasicErrorController,当然可以直接定义一个普通的controller类,直接实现第一步定义的视图的方法。重写的目的是重用ErrorAttributes。这样在页面,直接可以获取到status,message,exception,trace等内容。
如果仅仅是把异常处理的视图作为静态页面,不需要看到异常信息内容的话,直接第一步后,再定义error/404,error/500等静态视图即可。

ErrorController根据Accept头的内容,输出不同格式的错误响应。比如针对浏览器的请求生成html页面,针对其它请求生成json格式的返回

以上两步的操作,比网上流传的更能实现自定义化。

作者

Jonathan

发布于

2016-05-31

更新于

2019-06-10

许可协议