登录后台

页面导航

本文编写于 1065 天前,最后修改于 264 天前,其中某些信息可能已经过时。

覆盖默认的错误处理方式

默认错误处理机制的响应内容格式不一定是你相中的。理由可能如下:

  1. Whitelabel Error Page 页面的样式太单调,用户体验不好。
  2. Json 格式的结果字符串不统一,与你配合的前端人员更希望统一格式,好做统一的显示处理。比如与前端人员配合时统一指定响应结果格式为 {status:true,msg:”xxx”,data:{xxx}},但 Spring Boot 的 Json 格式是 {status:500,message:”error occur”,path:”/exception”……}

那么你可能更期望可以修改默认的处理方式,改变响应内容格式。Spring Boot 开发指南上给出了几种方法。

  1. 自定义一个bean,实现 ErrorController 接口,那么默认的错误处理机制将不再生效。
  2. 自定义一个 bean,继承 BasicErrorController 类,使用一部分现成的功能,自己也可以添加新的public方法,使用 @RequestMapping 及其 produces 属性指定新的地址映射。
  3. 自定义一个 ErrorAttribute 类型的 bean,那么还是默认的两种响应方式,只不过改变了内容项而已。
  4. 继承 AbstractErrorController。

BasicErrorController源码

SpringBoot 在页面 发生异常的时候会自动把请求转到 /error,SpringBoot 内置了一个 BasicErrorController 对异常进行统一的处理,当然也可以自定义这个路径。

server:
  port: 8080
  error:
    path: /custom/error

BasicErrorController 提供两种返回错误一种是页面返回、当你是页面请求的时候就会返回页面,另外一种是 json 请求的时候就会返回 json 错误:

@RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }

分别会有如下两种返回

spring_boot_error_type

自定义一个bean,继承BasicErrorController类

BasicErrorController,这个是 SpringBoot 的默认错误处理,也是一种全局处理方式。咱们可以模仿这种处理方式自定义自己的全局错误处理 下面定义了一个自己的 BasicErrorController,可以根据自己的需求自定义 errorHtml() 和 error() 的返回值。

SpringBoot提供了一种特殊的Bean定义方式,可以让我们容易的覆盖已经定义好的Controller,原生的BasicErrorController是定义在ErrorMvcAutoConfiguration中的 具体代码如下:

    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }

可以看到这个注解 @ConditionalOnMissingBean 意思就是定义这个 bean 当 ErrorController.class 这个没有定义的时候, 意思就是说只要我们在代码里面定义了自己的ErrorController.class 时,这段代码就不生效了。

自定义 MyErrorController:

package com.lf.exception;

import org.springframework.boot.autoconfigure.web.BasicErrorController;
import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by LF on 2017/4/16.
 */
@Controller
public class MyErrorController extends BasicErrorController {

    public MyErrorController(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }

    /**
     * 覆盖默认的Json响应
     */
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);

        //输出自定义的Json格式
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", false);
        map.put("msg", body.get("message"));

        return new ResponseEntity<Map<String, Object>>(map, status);
    }

    /**
     * 覆盖默认的HTML响应
     */
    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        //请求的状态
        HttpStatus status = getStatus(request);
        response.setStatus(getStatus(request).value());

        Map<String, Object> model = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        //指定自定义的视图
        return(modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }
}

总结

其实查看 BasicErrorController 源码,响应结果不论是 html 还是 json,内容源都是 ErrorAttribute。第三种方法只能改变内容,却改变不了格式,特别是 html 页面的样式,笔者就不予考虑了。

无论第一种还是第二种办法,都需要你看下BasicErrorController的继承体系及实现,因为 BasicErrorController 也是实现了 ErrorController。

采用第一种方式你可以具有完全的控制权,你可以摒弃默认的 “Whitelabel Error Page”,指定自己的视图及视图样式,你可以指定响应的Json格式内容等等,因为 BasicErrorController 不再起作用。

参照了 BasicErrorController 的源码,感觉第二种方法可能更简便些,所以实现了第二种方法。第二种方法的思路其实就是你可以通过继承,利用 BasicErrorController 已有的功能,或者进行扩展。

本文转载自:https://blog.csdn.net/L_Sail/article/details/70198886