浏览器配置异常_SpringBoot 2.2和2.3异常处理的一个小变化

先看个有意思的现象:

@RestControllerpublic class DemoController {    @GetMapping("/hello")    public String hello(){        throw new RuntimeException("服务端异常,请稍后再试!");    }}

在springboot2.2.0的时候,浏览器访问http://localhost:8080/hello,输出结果如下:

6fdeb52df398a8826a3d6dd3e3713fd6.png

换成springboot2.3.0的时候,输出结果如下:

970d0250f095ed9b8b0cb766a1b7be37.png

对比一下,能看出来在2.3.0的时候,异常message没有打印出来。

以上是在返回html的时候,返回json的时候,我们再来看下:

public static void main(String[] args)throws Exception {  String url = "http://localhost:8080/hello";  HttpURLConnection conn = (HttpURLConnection)new URL(url).openConnection();  conn.addRequestProperty("Accept", "application/json");  int code = conn.getResponseCode();  System.out.println("responseCode:" + code);  InputStream inputStream = conn.getErrorStream();  String response = readInputStream(inputStream);  System.out.println(response);}

在2.2.0的时候输出结果是:

{"timestamp":"2020-08-16T04:18:30.778+0000","status":500,"error":"Internal Server Error","message":"服务端异常,请稍后再试!","path":"/hello"}

在2.3.0的时候输出结果是:

{"timestamp":"2020-08-16T04:16:25.738+00:00","status":500,"error":"Internal Server Error","message":"","path":"/hello"}

这回看的更清晰了,message字段变成了空。

看下源码的处理,springboot异常处理的入口配置类在这里:ErrorMvcAutoConfiguration,先看2.2.0,它里面配置了处理error的BasicErrorController:

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

看下BasicErrorController里面是如何响应error的:

//这个就是生成html页面额@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)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);}//这个是输出json的@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {  HttpStatus status = getStatus(request);  if (status == HttpStatus.NO_CONTENT) {    return new ResponseEntity<Map<String, Object>>(status);  }  Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));  return new ResponseEntity<>(body, status);}

我们主要看下页面上的数据是怎么生成的,也就是getErrorAttributes():

在这之前首先判断了是否要包含异常堆栈isIncludeStackTrace():

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;}

是根据配置项server.error.includeStacktrace来决定的,默认是IncludeStacktrace.NEVER。如果配置了是ALWAYS那就输出堆栈,如果配置了ON_TRACE_PARAM,那么就从请求参数中获取trace参数,如果是true那么也会打印堆栈。

protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {  WebRequest webRequest = new ServletWebRequest(request);  return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);}@Overridepublic Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {  Map<String, Object> errorAttributes = new LinkedHashMap<>();  //timestamp  errorAttributes.put("timestamp", new Date());  //status  addStatus(errorAttributes, webRequest);  //error和message  addErrorDetails(errorAttributes, webRequest, includeStackTrace);  //path  addPath(errorAttributes, webRequest);  return errorAttributes;}

这里看到了,这里面包含的内容首先是timestamp,然后是status,最后一行是path,看下addErrorDetails():

private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,    boolean includeStackTrace) {    //获取异常,上例中就是RuntimeException  Throwable error = getError(webRequest);  if (error != null) {    while (error instanceof ServletException && error.getCause() != null) {      error = error.getCause();    }    //这个includeException也是个配置项server.error.includeException    // 如果打开就输出exception,值是异常的类名    // 默认是没有打开的,可以看下DefaultErrorAttributes    if (this.includeException) {      errorAttributes.put("exception", error.getClass().getName());    }    // 添加message属性    addErrorMessage(errorAttributes, error);    //是否包含异常堆栈,默认也是没有的    if (includeStackTrace) {      // 添加异常堆栈      addStackTrace(errorAttributes, error);    }  }  Object message = getAttribute(webRequest, "javax.servlet.error.message");  if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)      && !(error instanceof BindingResult)) {    errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);  }}private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {  BindingResult result = extractBindingResult(error);  //显然这里是null,因此直接直接添加error.getMessage()  if (result == null) {    errorAttributes.put("message", error.getMessage());    return;  }  if (result.hasErrors()) {    errorAttributes.put("errors", result.getAllErrors());    errorAttributes.put("message", "Validation failed for object='" + result.getObjectName()        + "'. Error count: " + result.getErrorCount());  }  else {    errorAttributes.put("message", "No errors");  }}

现在我们试着添加下那几个属性,看下输出结果,首先添加server.error.includeStacktrace= IncludeStacktrace.ALWAYS:

9ae9049e6163daa10b33f9f3ec8f79ae.png

再添加下server.error.includeException=true:

{"timestamp":"2020-08-16T04:56:26.275+0000","status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException",//多了一个这个"message":"服务端异常,请稍后再试!","trace":"java.lang.RuntimeException: 服务端异常,请稍后再试!\r\n\tat com.github.xjs.error.controller.DemoController.hello(DemoController.java:10)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:634)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n","path":"/hello"}

我们再来看下2.3.0:

@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {  HttpStatus status = getStatus(request);  if (status == HttpStatus.NO_CONTENT) {    return new ResponseEntity<>(status);  }  Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));  return new ResponseEntity<>(body, status);}

这里的getErrorAttributes之前,首先是getErrorAttributeOptions():

protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {  ErrorAttributeOptions options = ErrorAttributeOptions.defaults();  //server.error.includeException,是否输出异常类名  if (this.errorProperties.isIncludeException()) {    options = options.including(Include.EXCEPTION);  }  //是否打印堆栈,server.error.includeStacktrace= IncludeStacktrace.NEVER  if (isIncludeStackTrace(request, mediaType)) {    options = options.including(Include.STACK_TRACE);  }  //是否输出message  if (isIncludeMessage(request, mediaType)) {    options = options.including(Include.MESSAGE);  }  //和上面是使用的同一个配置项  if (isIncludeBindingErrors(request, mediaType)) {    options = options.including(Include.BINDING_ERRORS);  }  return options;}

是否输出堆栈,跟2.2.0一样的逻辑:

//server.error.includeStacktrace= IncludeStacktrace.NEVERprotected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {  switch (getErrorProperties().getIncludeStacktrace()) {  case ALWAYS:    return true;  case ON_PARAM:  case ON_TRACE_PARAM:    return getTraceParameter(request);  default:    return false;  }}

是否输出message:

//添加了一个新的配置项,2.2.0是直接输出的//server.error.includeMessage=IncludeAttribute.NEVER;protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {  switch (getErrorProperties().getIncludeMessage()) {  case ALWAYS:    return true;  case ON_PARAM:    return getMessageParameter(request);  default:    return false;  }}

2.3.0新增了一个配置项server.error.includeMessage,默认是NEVER,因此默认是不是输出message的,只要开启就可以了。

当开启以下参数的时候:

server:  error:    includeException: true    includeStacktrace: ALWAYS    includeMessage: ALWAYS

输出结果:

{"timestamp":"2020-08-16T05:09:23.983+00:00","status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","trace":"java.lang.RuntimeException: 服务端异常,请稍后再试!\r\n\tat com.github.xjs.error.controller.DemoController.hello(DemoController.java:10)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:634)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n","message":"服务端异常,请稍后再试!","path":"/hello"}

我们可以模仿BasicErrorController自定义一下异常的处理:

@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class MyErrorController implements ErrorController {    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)throws Exception {        response.setContentType("text/html;charset=UTF-8");        response.getWriter().append("服务端异常,请稍后重试");        return null;    }    @RequestMapping    public ResponseEntityString,         Map<String, Object> body = new HashMap<>();        body.put("message", "服务端异常,请稍后重试");        return new ResponseEntity(body, HttpStatus.INTERNAL_SERVER_ERROR);    }    @Override    public String getErrorPath() {        return null;    }}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值