springboot继承AbstractErrorController实现全局的异常处理

项目中常常需要一个全局异常,防止未处理的异常信息直接暴露给用户,影响用户体验。springboot中可以使用ControllerAdvice和ExceptionHandler这两个注解来做全局异常,这种方式比较便捷,但是也有一个问题:
ContollerAdvice只能拦截控制器中的异常,换言之,只能拦截500之类的异常,但是对于404这样不会进入控制器处理的异常不起作用。所以我仿造springboot默认的全局处理类BasicController实现全局的异常处理,这样就能很好的按照自己的需求处理异常了。

我们先了解一下springboot默认的异常处理是怎样的:
springboot会将所有的异常发送到路径为server.error.path(application.properties中可配置,默认为”/error”)的控制器方法中进行处理,页面请求和ajax请求会分别打到对应的处理方法上。具体的处理可查看BasicErrorController的源代码:

    @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) ? modelAndView : new ModelAndView("error", model);
    }

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

了解完springboot的默认全局异常处理后,开始仿造着写自定义的异常处理,BasicErrorController继承了AbstractErrorController,所以我们也继承AbstractErrorController。整体代码如下:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@Slf4j
public class GlobalExceptionController extends AbstractErrorController{
    private final ErrorProperties errorProperties;
    @Autowired
    public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties=serverProperties.getError();
    }

    @Override
    public String getErrorPath() {
        return errorProperties.getPath();
    }

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        ModelAndView modelAndView=new ModelAndView("error");
        Map errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        if(errorMap!=null) {
            /*timestamp status error message path*/
            modelAndView.addObject("msg",errorMap.get("error"));
            modelAndView.addObject("statusCode",errorMap.get("status"));
            logHandler(errorMap);
        }
        return modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ServiceResponse error(HttpServletRequest request) {
        Map errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
        logHandler(errorMap);
        return ServiceResponse.createByError("测试成功:");
    }

    private void logHandler(Map errorMap) {
        log.error("url:{},status{},time:{},errorMsg:{}",errorMap.get("path"),errorMap.get("status"),errorMap.get("timestamp"),errorMap.get("message"));
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request,
            MediaType produces) {
        IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
    private ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
} 
  

代码讲解:
1.开头注解
@Controller
@RequestMapping(“${server.error.path:${error.path:/error}}”)
这里与BasicErrorControlelr一致,使用server.error.path的变量作为映射地址。
2.构造函数

    @Autowired
    public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties=serverProperties.getError();
    }

本来是抄BasicErrorController中的构造方法:

    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

但是程序启动的室友报错了,原因是无法找到errorProperties的实体。于是,我便查看在自动注入BasicErrorController的类ErrorMvcAutoConfiguration,在里面发现errorPropertie属性原来是通过ServerProperties这个类注入的:

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

于是便在自定义的异常处理类构造方法中使用ServerProperties 注入errorProperties。
3.编写自定义的异常处理方法。
通过查看AbstractErrorController和BasicErrorController源码,我发现异常信息都是通过

    protected Map getErrorAttributes(HttpServletRequest request,
            boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

这个方法获取的。我还发现ErrorAttributes这个类中有一个接口可以直接获取异常:

    /**
     * Return the underlying cause of the error or {@code null} if the error cannot be
     * extracted.
     * @param webRequest the source request
     * @return the {@link Exception} that caused the error or {@code null}
     */
    Throwable getError(WebRequest webRequest);

但是,测试之后发现通过这个getError获取的异常都是null,一看上面的注释,才发现这个方法只能获取未被抽取出来的异常,而我们拦截的异常都被抽取出来了,所以获取不到。
所以还是仿造BasicErrorControler实现我们自己的异常处理:

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        ModelAndView modelAndView=new ModelAndView("error");
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        if(errorMap!=null) {
            /*timestamp status error message path*/
            modelAndView.addObject("msg",errorMap.get("error"));
            modelAndView.addObject("statusCode",errorMap.get("status"));
            logHandler(errorMap);
        }
        return modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ServiceResponse error(HttpServletRequest request) {
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
        logHandler(errorMap);
        return ServiceResponse.createByError(errorMap.get("error"));
    } 
  

ServiceResponse是我自己定义的ajax返回实体类.getErrorAttributes会将异常消息封装成一个map,包含了异常的主要信息:
timestamp:异常发生的时间
status :http响应状态码
error:异常的名称(Exception.getName())
message:异常的消息(getmessage())
path:发生异常的路由。

这样做其实还是一点没有做好,那就是无法获取到Exception异常对象。后面有时间再想想怎么弄。
还有一点要特别注意的地方,那就是一定要保证自己的异常处理代码不能再发生异常,否则异常会还会被自定义的处理器接受,造成死循环。

你可能感兴趣的:(springboot,异常处理)