当前位置: 首页 > 科技观察

你觉得SpringBoot的统一异常处理能拦截所有的异常吗?

时间:2023-03-13 00:43:25 科技观察

通常我们在SpringBoot中设置的统一异常处理只能处理Controller抛出的异常。有些请求在到达Controller之前就已经发生了异常,而这些异常无法被统一的异常捕获,比如Servlet容器的一些异常。今天在项目开发中遇到一个,让我很苦恼,因为它返回的错误信息的格式无法统一处理,于是决定想办法解决这个问题。ErrorPageFilterWhitelabelErrorPage这样的图相信大家也很常见吧。只要SpringBoot出错,页面上反映的就是这个。如果您使用Postman之类的工具来测试异常:{"timestamp":"2021-04-29T22:45:33.231+0000","status":500,"message":"InternalServerError","path":"foo/bar"}这是如何实现的?SpringBoot在启动时会注册一个ErrorPageFilter。当Servlet发生异常时,过滤器会根据不同的策略拦截并处理异常:当异常已经被处理时,则直接处理,否则转发到相应的错误页面。有兴趣的可以看看源码,逻辑并不复杂,这里就不贴了。另外,当一个Servlet抛出异常时,处理异常的Servlet可以从HttpServletRequest中得到几个属性,如下:异常属性我们可以从上面的属性中得到异常的详细信息。默认的错误页面通常在SpringBoot中出现异常时默认跳转到/error进行处理,/error的相关逻辑由BasicErrorController实现。@Controller@RequestMapping("${server.error.path:${error.path:/error}}")publicclassBasicErrorControllerextendsAbstractErrorController{//返回错误页面@RequestMapping(produces=MediaType.TEXT_HTML_VALUE)publicModelAndViewerrorHtml(HttpServletRequestrequest,HttpServletResponseresponse){atusH=getStatus(request);Mapmodel=Collections.unmodifiableMap(getErrorAttributes(request,getErrorAttributeOptions(request,MediaType.TEXT_HTML)));response.setStatus(status.value());ModelAndViewmodelAndView=resolveErrorView(请求,response,status,model);return(modelAndView!=null)?modelAndView:newModelAndView("error",model);}//返回json@RequestMappingpublicResponseEntity>error(HttpServletRequestrequest){HttpStatusstatus=getStatus(请求);if(status==HttpStatus.NO_CONTENT){returnnewResponseEntity<>(status);}Mapbody=getErrorAttributes(request,getErrorAttributeOptions(request,媒体类型.ALL));returnnewResponseEntity<>(body,status);}//其他省略}及对应配置:@Bean@ConditionalOnMissingBean(value=ErrorController.class,search=SearchStrategy.CURRENT)publicBasicErrorControllerbasicErrorController(ErrorAttributeserrorAttributes,ObjectProvidererrorViewResolvers){returnnewBasicErrorController(errorAttributes,this.serverProperties.getError(),errorViewResolvers.orderedStream().collect(Collectors.toList()));}所以我们只需要重新实现一个ErrorController并注入SpringIoC来替换默认的处理机制就可以清楚了发现这个BasicErrorController不仅是ErrorController的实现,还是一个控制器。如果我们让controller的方法抛出异常,那肯定可以用自定义的统一异常来处理。所以我对BasicErrorController进行了修改:@Controller@RequestMapping("${server.error.path:${error.path:/error}}")publicclassExceptionControllerextendsAbstractErrorController{publicExceptionController(ErrorAttributeserrorAttributes){super(errorAttributes);}@Override@已弃用的publicStringgetErrorPath(){returnnull;}@RequestMapping(produces=MediaType.TEXT_HTML_VALUE)publicModelAndViewerrorHtml(HttpServletRequestrequest,HttpServletResponseresponse){thrownewRuntimeException(getErrorMessage(request));}@RequestMappingpublicResponseEntity>error(HttpServletRequestrequest){RequestErrorrequest(HttpServletRequestRquest)(请求));}privateStringgetErrorMessage(HttpServletRequestrequest){Objectcode=request.getAttribute("javax.servlet.error.status_code");ObjectexceptionType=request.getAttribute("javax.servlet.error.exception_type");Objectmessage=request.getAttribute("javax.servlet.error.message");对象路径=request.getAttribute("javax.servlet.error.request_uri");Objectexception=request.getAttribute("javax.servlet.error.exception");returnString.format("code:%s,exceptionType:%s,message:%s,path:%s,exception:%s",code,exceptionType,message,path,exception);}}直接抛异常,简单又省力!这里捕获到的异常大部分都没有经过Controller,我们通过ExceptionController中继,也让这些异常得到统一的处理,保证整个应用的异常处理对外保持统一的外观