栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

关于 SpringBoot 默认异常信息返回问题梳理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

关于 SpringBoot 默认异常信息返回问题梳理

关于 SpringBoot 默认异常信息返回问题整理
        • 关于 SpringBoot 默认异常信息返回问题整理
          • 一、我的疑问
          • 二、具体问题具体分析
          • 三、温馨提示
          • 四、我的总结

关于 SpringBoot 默认异常信息返回问题整理 一、我的疑问

本文从如下两个问题开展讨论分析:

  1. 接口抛出 RuntimeException 后 Spring 给我们做了什么?

  2. 如何自定义默认异常信息返回?

二、具体问题具体分析

先来解释一下第一个问题,SpringMVC 在接口 throw RuntimeException 后通过 DispatcherServlet 的 processDispatchResult 处理异常,我想这个应该大家都知道,我想说的是大家进行断点的时候回发现会再次调用 /error 地址,这是因为 ErrorPageCustomize 注册了一个 ErrorPage ,所以出现错误以后来到 error 请求进行处理。

到了这里我们知道 SpringBoot 会调用 /error 请求,那么大家肯定都知道会有一个 Controller 来处理吧,对 Spring 给了一个默认的 Controller 来处理 /error 请求,它就是 BasicErrorController ; BasicErrorController 会将错误信息返回成一个 ResponseEntity 或 ModelAndView,这个根据我们的接口实现来确定到底是返回 ResponseEntity 或 ModelAndView。

另外大家肯定有一个疑问,异常信息的堆栈信息是如何传递的?因为上面说到 SpringBoot 接口异常后会再次请求 /error,这里解释一下,请求我们业务接口的 request 对象和 /error 是同一个对象,既然是同一个对象那异常信息传递还难吗?我下面复制了部分源码:

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {

	@Nullable
	private List resolvers;

	private int order = Ordered.LOWEST_PRECEDENCE;


	
	public void setExceptionResolvers(List exceptionResolvers) {
		this.resolvers = exceptionResolvers;
	}

	
	public List getExceptionResolvers() {
		return (this.resolvers != null ? Collections.unmodifiableList(this.resolvers) : Collections.emptyList());
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}


	
	@Override
	@Nullable
	public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		if (this.resolvers != null) {
			for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
				ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (mav != null) {
					return mav;
				}
			}
		}
		return null;
	}
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

   private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";

   private final Boolean includeException;

   
   public DefaultErrorAttributes() {
   	this.includeException = null;
   }

   
   @Deprecated
   public DefaultErrorAttributes(boolean includeException) {
   	this.includeException = includeException;
   }

   @Override
   public int getOrder() {
   	return Ordered.HIGHEST_PRECEDENCE;
   }

   @Override
   public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
   		Exception ex) {
   	storeErrorAttributes(request, ex);
   	return null;
   }

   private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
   	request.setAttribute(ERROR_ATTRIBUTE, ex);
   }

   @Override
   public Throwable getError(WebRequest webRequest) {
   	Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
   	return (exception != null) ? exception : getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);
   }

   @SuppressWarnings("unchecked")
   private  T getAttribute(RequestAttributes requestAttributes, String name) {
   	return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
   }

}

请求接口后调用链:

DispatcherServlet.doDispatch -> DispatcherServlet.processDispatchResult -> DispatcherServlet.processHandlerException -> HandlerExceptionResolverComposite.resolveException -> DefaultErrorAttributes.resolveException,一层套一层。

从上述源码不难看出, request.setAttribute(ERROR_ATTRIBUTE, ex); 将异常信息存入,最后通过

requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST); 获取异常信息,这样就实现了异常信息的传递。

到这第一个问题应该是解释清楚了。

第二个问题我们如何自定义默认异常信息返回?我想大家都比较好奇为什么是自定义默认异常信息的返回,而不是像大部分博客写得一样增加一个统一异常处理机制? 对,我们的统一异常处理机制不在服务中,而是在其他的地方,这样我们只需要自定义 SpringBoot 的默认异常信息即可。重点来了,我想大家应该知道 SpringBoot 在异常后会返回类似的如下信息:

{
    "timestamp": "2021-10-14T05:31:59.613+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Request processing failed; nested exception is com.exception.ErrorException: 我是异常",
    "path": "/test"
}

其实 SpringBoot 帮我们过滤了除上述 timestamp、 status 、error 、message 、path 外还可以返回 exception (异常类路径)、trace (堆栈详细信息),类似这样:

{
    "timestamp": "2021-10-14T06:25:56.226+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "exception": "com.exception.ErrorException",
    "trace": "com.exception.ErrorException: {"errorCode": 30001, "systemId": "Not Definition", "message": "我是异常"}rntat com.template.TestController.test(TestController.java:45)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)rntat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)rntat java.base/java.lang.reflect.Method.invoke(Method.java:566)rntat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)rntat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)rntat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)rntat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)rntat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1064)rntat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)rntat org.springframework.web.servlet.frameworkServlet.processRequest(frameworkServlet.java:1006)rntat org.springframework.web.servlet.frameworkServlet.doGet(frameworkServlet.java:898)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:497)rntat org.springframework.web.servlet.frameworkServlet.service(frameworkServlet.java:883)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:584)rntat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)rntat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)rntat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)rntat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)rntat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)rntat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)rntat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:97)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)rntat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)rntat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)rntat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)rntat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)rntat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)rntat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)rntat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)rntat io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)rntat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)rntat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)rntat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)rntat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)rntat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)rntat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)rntat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)rntat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)rntat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)rntat io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)rntat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)rntat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:280)rntat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)rntat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)rntat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)rntat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)rntat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)rntat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:260)rntat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)rntat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)rntat io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)rntat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)rntat org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)rntat org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)rntat org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)rntat org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423)rntat org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)rntat java.base/java.lang.Thread.run(Thread.java:834)rn",
    "message": "Request processing failed; nested exception is com.exception.ErrorException: 我是异常,
    "path": "/test"
}

其中 exception(异常类路径)和trace (堆栈详细信息)是默认不显示的,是不是很神奇。可以通过配置进行配置:

public class ErrorProperties {

	
	@Value("${error.path:/error}")
	private String path = "/error";

	
	private boolean includeException;

	
	private IncludeStacktrace includeStacktrace = IncludeStacktrace.NEVER;

	
	private IncludeAttribute includeMessage = IncludeAttribute.NEVER;

	
	private IncludeAttribute includeBindingErrors = IncludeAttribute.NEVER;

	private final Whitelabel whitelabel = new Whitelabel();

	public String getPath() {
		return this.path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public boolean isIncludeException() {
		return this.includeException;
	}

	public void setIncludeException(boolean includeException) {
		this.includeException = includeException;
	}

	public IncludeStacktrace getIncludeStacktrace() {
		return this.includeStacktrace;
	}

	public void setIncludeStacktrace(IncludeStacktrace includeStacktrace) {
		this.includeStacktrace = includeStacktrace;
	}

	public IncludeAttribute getIncludeMessage() {
		return this.includeMessage;
	}

	public void setIncludeMessage(IncludeAttribute includeMessage) {
		this.includeMessage = includeMessage;
	}

	public IncludeAttribute getIncludeBindingErrors() {
		return this.includeBindingErrors;
	}

	public void setIncludeBindingErrors(IncludeAttribute includeBindingErrors) {
		this.includeBindingErrors = includeBindingErrors;
	}

	public Whitelabel getWhitelabel() {
		return this.whitelabel;
	}

	
	public enum IncludeStacktrace {

		
		NEVER,

		
		ALWAYS,

		
		ON_PARAM,

		
		@Deprecated // since 2.3.0 in favor of {@link #ON_PARAM}
		ON_TRACE_PARAM;

	}

	
	public enum IncludeAttribute {

		
		NEVER,

		
		ALWAYS,

		
		ON_PARAM

	}

	public static class Whitelabel {

		
		private boolean enabled = true;

		public boolean isEnabled() {
			return this.enabled;
		}

		public void setEnabled(boolean enabled) {
			this.enabled = enabled;
		}

	}

}

以上是 SpringBoot 2.4.X 的 ErrorProperties 配置文件,在配置文件中做如下配置:

server:
  port: 8083
  error:
    include-message: always

最后提一句: SpringBoot 2.3.X 后 message 默认不返回,如下所示:

{
    "timestamp": "2021-10-14T05:31:59.613+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/test"
}
三、温馨提示

最后提醒一句:SpringBoot 2.3.X 后 message 默认不返回通过配置 server.error.include-message=always可返回。

最后提醒一句:SpringBoot 2.3.X 后 message 默认不返回通过配置 server.error.include-message=always可返回。

最后提醒一句:SpringBoot 2.3.X 后 message 默认不返回通过配置 server.error.include-message=always可返回。

四、我的总结

刚开始排查 message 无法返回信息的时候发现是升级了 SpringBoot 后导致的,之前 SpringBoot 2.2.5 版本是没问题的,但是升级到 SpringBoot 2.4.11 后就出现问题了,所以第一反应是去查询官网,但是官网只是写了个大概,下图:

If an exception occurs during request mapping or is thrown from a request handler (such as a @Controller), the DispatcherServlet delegates to a chain of HandlerExceptionResolver beans to resolve the exception and provide alternative handling, which is typically an error response.

被这句话点醒了,所以排查问题时只能进行 debug,一步一步的查找原因,费了很大劲。但是总体感觉排查问题的方向是对的,还是值了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/329134.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号