Spring统一异常处理器无法处理非法请求异常

1.问题描述

Forwarding to error page from request [/aaa/bbb/ccc/bookhistory/] due to exception [账号未登录] com.xxx.web.base.exception.BusinessException: 账号未登录
at com.xxx.web.core.interceptor.LoginInterceptor.preHandle(LoginInterceptor.java:20)
at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:136)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:986)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.sankuai.oceanus.http.filter.InfFilter.doFilter(InfFilter.java:94)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at 
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:130)
at org.springframework.boot.web.servlet.support.ErrorPageFilter.access$000(ErrorPageFilter.java:66)
at org.springframework.boot.web.servlet.support.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

对登录校验,做了一个拦截器,统一校验,对于未登录用户抛出自定义异常,然后使用Spring的统一异常处理统一处理,然而还是报错了。

##2.具体问题
正常来说,哪怕用户未登录,也不是抛出异常,而是友好提示,如:项目中拦截器抛出的未登录异常,正常来说是会被程序中的异常统一处理器,统一处理:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public RespNVO<String> businessExceptionHandle(HttpServletRequest request,BusinessException e){
        return RespNVO.error(e.getCode(),e.getMessage());
    }
	//...略过部分代码
}

为什么这个异常未被统一处理器处理掉?原因在于请求路径有问题:这个请求是非法请求URL,例如“/aaa/bbb/ccc/bookhistory/”,项目中并没有对应的请求处理方法,我们来看Spring对于这种请求抛出的异常是如何处理的:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 获取当前请求路径的处理handler:mappedHandler,包括具体处理该请求的handler以及对该请求其作用的相关拦截器
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 获取具体处理请求的适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//处理拦截器前置请求,本次在这里会抛出异常,由下方代码catch住
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 处理请求
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				
				applyDefaultViewName(processedRequest, mv);
        //拦截器后置处理
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
        //异常缓存
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
      //不管是否有异常,均需要执行processDispatchResult
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

1.如果处理请求中未抛出异常,则走的是非法请求路径的处理方法:

非法请求路径的时候,mappedHandler的值:

   HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@433ab5fd]]] and 3 interceptors

其中ResourceHttpRequestHandler是Spring对http请求静态资源的一种描述(六个资源路径:META-INF/resources/、resources/、static/、public/、/,以及默认的[]),由PathResourceResolver负责寻找对应路径资源。

如果资源未找到,则跳到springboot默认错误页面(如果未配置对应的错误跳转页面的话):404:Whitelabel Error Page(具体见ErrorMvcAutoConfiguration)

对于正常请求,返回的mappedHandler的具体值如:

   HandlerExecutionChain with handler [com.vo.RespNVO<com.vo.CoachListVO> com.controller.CoachReadController.fetchCoachList(java.lang.Integer,java.lang.Integer)] and 4 interceptors

其handler类型为HandlerMethod,其值包含相应的处理方法相关信息。

   handler的类型对之后的异常处理流程是有影响的,详见后续分析。

2.请求中抛出异常:看代码知道,有异常的情况,异常会先被暂存在dispatchException,并传到processDispatchResult方法中进行处理:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
        //异常处理流程
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

processDispatchResult逻辑:有异常,进行异常处理逻辑:ModelAndViewDefiningException与其他异常处理。这里此次请求即会进入processHandlerException方法内,进行异常处理流程:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
				exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

具体异常处理,由handlerExceptionResolvers(List),即一系列的异常处理器处理,内置的有:DefaultErrorAttributes(缓存错误信息,供后面错误视图使用)以及HandlerExceptionResolverComposite(其内同样包含一个List,是一种有序的异常处理器)。

对于HandlerExceptionResolverComposite,其内置的异常处理器有如下三个:

第一个,ExceptionHandlerExceptionResolver,即上面提到的统一处理器入口。

第二个,ResponseStatusExceptionResolver,需要配合ResponseStatus使用,故本次情况可以忽略。

第三个,DefaultHandlerExceptionResolver,默认的异常处理器,是Spring提供的对默认一些异常的处理方法,比如常见的TypeMismatchException、MethodArgumentNotValidException、BindException等处理。

不管是第一个还是第三个异常处理器,其具体处理流程由AbstractHandlerExceptionResolver(是二者的抽象实现父类)实现:

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

		if (shouldApplyTo(request, handler)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
			}
			prepareResponse(ex, response);
			ModelAndView result = doResolveException(request, response, handler, ex);
			if (result != null) {
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

这里的重点是shouldApplyTo以及对应的doResolveException(具体实现由实现类实现)方法,shouldApplyTo这个方法是对以上内置的三个异常处理器是否可以处理本次请求异常的判定,具体判定,内置实现有两种:AbstractHandlerExceptionResolver以及其实现抽象类AbstractHandlerMethodExceptionResolver,其中上面说到的ExceptionHandlerExceptionResolver的直接抽象类为AbstractHandlerMethodExceptionResolver,而第三个DefaultHandlerExceptionResolver直接抽象类是AbstractHandlerExceptionResolver(具体类图见最后附录):

//AbstractHandlerExceptionResolver
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
  	//请求处理的handler是否为空
		if (handler != null) {
      //是否有手动设置的对应处异常理器
			if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
				return true;
			}
      //是否有手动异常处理类
			if (this.mappedHandlerClasses != null) {
				for (Class<?> handlerClass : this.mappedHandlerClasses) {
					if (handlerClass.isInstance(handler)) {
						return true;
					}
				}
			}
		}
		// 默认二者均为空,即所有handler均可进入异常处理
		return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
	}
//AbstractHandlerMethodExceptionResolver
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
		if (handler == null) {
      //为空,走默认判定流程
			return super.shouldApplyTo(request, null);
		}
		else if (handler instanceof HandlerMethod) {
      //如果是HandlerMethod类型
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			handler = handlerMethod.getBean();
			return super.shouldApplyTo(request, handler);
		}
		else {
			return false;
		}
	}

AbstractHandlerExceptionResolver的shouldApplyTo方法,默认是所有handler发生异常时均可进入异常处理流程的。

对于AbstractHandlerMethodExceptionResolver来说,若handler类型是HandlerMethod,会将请求处理类的bean当做handler放入异常处理判定中。

对于本次请求,handler类型为ResourceHttpRequestHandler,第一个内置异常处理器ExceptionHandlerExceptionResolver,shouldApplyTo返回false(所以,非法请求的异常是不会被统一异常处理器处理的),第三个返回true,即进入默认的异常处理器,而默认处理器中并没有处理本次自定义异常类型(BusinessException),故最终流程会走到processHandlerException方法的最后:重新抛出该异常。异常被再次抛出后将无后续异常处理方法,故前端收到的是后端抛出的异常代码。

3.结论

   对于非法请求,自定义的统一异常处理器无法处理其内发生的异常。

附录:

这里写图片描述

<think>好的,我需要解释如何在Spring Boot中非泛型地配置全局异常处理器。首先,用户提到的是“非泛类型配置”,可能是指不使用通用的Exception.class,而是针对具体的异常类型进行处理。接下来,我需要回忆Spring中的@ControllerAdvice和@ExceptionHandler的用法。 首先,全局异常处理通常通过@ControllerAdvice注解的类来实现,里面定义多个@ExceptionHandler方法处理不同的异常。非泛型可能意味着每个处理器方法专门处理一种特定的异常,而不是捕获所有异常。比如,处理NullPointerException和IllegalArgumentException分开,而不是用Exception.class作为参数。 然后,要说明具体步骤:创建带有@ControllerAdvice的类,定义不同的方法并用@ExceptionHandler指定具体的异常类型。每个方法返回适当的响应,如ResponseEntity或者自定义的错误对象。需要给出示例代码,展示如何处理不同的异常,并返回对应的HTTP状态码和消息。 同时,要强调这种方法的优点,比如更精确地控制每种异常处理逻辑,提供更具体的错误信息给客户端。可能还需要提到顺序问题,如果有多个处理器匹配同一个异常,更具体的异常会优先执行。此外,可以补充如何自定义异常类,并为其添加处理器,以扩展应用程序的异常处理能力。 需要检查是否遗漏了关键点,比如确保每个@ExceptionHandler方法处理的是特定的异常类型,而不是通用的父类。另外,可能还要提到在Spring Boot中如何测试这些异常处理器,例如通过单元测试或者发送特定请求触发异常。 最后,总结非泛型配置的好处,比如代码结构清晰,维护方便,错误响应更精准,提升API的友好性。确保语言简洁,示例代码正确,并且符合用户要求的格式,比如使用$$包围独立代码块,行内代码用$包围。</think>在Spring Boot中配置非泛型全局异常处理器,可以通过`@ControllerAdvice`结合`@ExceptionHandler`实现精准的异常类型匹配。以下是具体实现步骤和示例: $$ // 示例:非泛型全局异常处理器配置 @ControllerAdvice public class CustomGlobalExceptionHandler { // 专门处理空指针异常 @ExceptionHandler(NullPointerException.class) public ResponseEntity<ErrorResponse> handleNullPointer(NullPointerException ex) { ErrorResponse error = new ErrorResponse( "NULL_REFERENCE", "操作对象不存在", LocalDateTime.now() ); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } // 专门处理非法参数异常 @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException ex) { ErrorResponse error = new ErrorResponse( "INVALID_ARGUMENT", ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } // 处理自定义业务异常 @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) { ErrorResponse error = new ErrorResponse( ex.getErrorCode(), ex.getLocalizedMessage(), LocalDateTime.now() ); return new ResponseEntity<>(error, ex.getHttpStatus()); } } $$ **关键实践要点**: 1. 精准匹配原则:每个`@ExceptionHandler`处理$特定异常类型$,而非`Exception.class` 2. 响应结构化:使用统一错误响应体(示例中的`ErrorResponse`) 3. 状态码控制:根据异常类型返回合适的HTTP状态码 4. 异常继承处理:子类异常处理器优先于父类异常处理器 **优势对比**: | 处理方式 | 精准度 | 可维护性 | 错误信息质量 | |--------------|-------|---------|------------| | 泛型(Exception) | 低 | 差 | 一般 | | 非泛型 | 高 | 优 | 高 | **扩展建议**: 1. 创建自定义异常体系: $$ // 业务基类异常 public abstract class BusinessException extends RuntimeException { private final HttpStatus httpStatus; private final String errorCode; // 构造方法、getters... } $$ 2. 异常处理器优先级控制: ```java @Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级 @ControllerAdvice public class PrimaryExceptionHandler { // 处理最关键的异常 } ``` 3. 组合使用处理策略: - 使用`@ExceptionHandler`处理具体异常 - 保留一个兜底的泛型异常处理器 - 通过`@ResponseStatus`注解控制默认响应状态 这种配置方式使异常处理逻辑更清晰,便于后期维护扩展,同时为客户端提供准确的错误诊断信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值