parsed RequestPath in request attribute “org.springframework.web.util.ServletRequestPathUtils.PATH

一。问题描述

springboot版本升级到2.6.13 自定义的拦截器居然出错了
主要业务代码如下

主要根据请求获取 对应的HandlerMethod

private HandlerMethod getHandlerMethod(HttpServletRequest request) {
	 Map<String, HandlerMapping> requestMappings = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
	         HandlerMapping.class, true, false);
	 try {
	     for (HandlerMapping handlerMapping : requestMappings.values()) {
	         HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
	         if (handlerExecutionChain != null && handlerExecutionChain.getHandler() instanceof HandlerMethod) {
	             return (HandlerMethod) handlerExecutionChain.getHandler();
	         }
	     }
	 } catch (Exception e) {
	     logger.error("获取URL{}对应的处理方法出现异常:{}", request.getServletPath(), e);
	 }
	
	 return null;
}

异常如下

java.lang.IllegalArgumentException: Expected parsed RequestPath in request attribute "org.springframework.web.util.ServletRequestPathUtils.PATH".
	at org.springframework.util.Assert.notNull(Assert.java:219)
	at org.springframework.web.util.ServletRequestPathUtils.getParsedRequestPath(ServletRequestPathUtils.java:77)
	at org.springframework.web.servlet.handler.AbstractHandlerMapping.initLookupPath(AbstractHandlerMapping.java:574)
	at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:380)
	at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:125)
	at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:67)
	at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:498)
	at com.style.common.permission.LoginCheckFilter.getHandlerMethod(LoginCheckFilter.java:84)
	at com.style.common.permission.LoginCheckFilter.doFilter(LoginCheckFilter.java:62)

二。 错误分析

主要是因为 请求 路径解析失败 可以从如下代码看到


protected String initLookupPath(HttpServletRequest request) {
   if (usesPathPatterns()) {
     request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
     RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
     String lookupPath = requestPath.pathWithinApplication().value();
     return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
   }
   else {
     return getUrlPathHelper().resolveAndCacheLookupPath(request);
   }
}


public static RequestPath getParsedRequestPath(ServletRequest request) {
    RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
    Assert.notNull(path, () -> "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
    return path;
}

直接从请求中设置的值获取 对应的path 如果获取失败就会看到错误提示。

三。解决

  • 修改路径解析的方式
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

正文结束

四。 源码分析

先下报错位置的代码

protected String initLookupPath(HttpServletRequest request) {
    if (usesPathPatterns()) {
      request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
      RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
      String lookupPath = requestPath.pathWithinApplication().value();
      return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
    }
    else {
      return getUrlPathHelper().resolveAndCacheLookupPath(request);
    }
}


public static RequestPath getParsedRequestPath(ServletRequest request) {
     RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
     Assert.notNull(path, () -> "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
     return path;
}

在usesPathPatterns 方法中进行判断 是否存在 首先结论肯定是存在,报错误日志已经提示调用了下面的方法

那我们还是来验证一下

org.springframework.web.servlet.handler.AbstractHandlerMapping#usesPathPatterns

@Override
public boolean usesPathPatterns() {
  	return getPatternParser() != null;
}

@Nullable
public PathPatternParser getPatternParser() {
  	return this.patternParser;
}

然后看下 patternParser 这个成员变量在哪里被赋值

其中只有一个 set方法 来设置patternParser

public void setPatternParser(PathPatternParser patternParser) {
		this.patternParser = patternParser;
}

再来看下被调用的地方 被调用的地址有点多 为了避免找错,我们直接断点看下
parsed RequestPath in request attribute “org.springframework.web.util.ServletRequestPathUtils.PATH_第1张图片
调用的链路如下

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#setPatternParser

@Override
public void setPatternParser(PathPatternParser patternParser) {
    Assert.state(this.mappingRegistry.getRegistrations().isEmpty(),
                 "PathPatternParser must be set before the initialization of " +
                 "request mappings through InitializingBean#afterPropertiesSet.");
    super.setPatternParser(patternParser);
}

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping

@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
  @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
    mapping.setOrder(0);
    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    mapping.setContentNegotiationManager(contentNegotiationManager);
    mapping.setCorsConfigurations(getCorsConfigurations());
		//重点在这里 
    PathMatchConfigurer pathConfig = getPathMatchConfigurer();
    if (pathConfig.getPatternParser() != null) {
      //调用位置
      mapping.setPatternParser(pathConfig.getPatternParser());
    }
    //省略部分代码
     
    return mapping;
}

调用路径整体的流程如上

但是我们再来看下 具体 细节

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getPathMatchConfigurer

protected PathMatchConfigurer getPathMatchConfigurer() {
    if (this.pathMatchConfigurer == null) {
      //为空 则new一个
      this.pathMatchConfigurer = new PathMatchConfigurer();
      //使用钩子方法进行配置
      configurePathMatch(this.pathMatchConfigurer);
    }
    return this.pathMatchConfigurer;
}

configurePathMatch 具体的钩子方法在

org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#configurePathMatch

@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
   this.configurers.configurePathMatch(configurer);
}

具体的调用又在

org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite#configurePathMatch

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
   for (WebMvcConfigurer delegate : this.delegates) {
      delegate.configurePathMatch(configurer);
   }
}


最后遍历delegates 调用 configurePathMatch方法 进行配置 这里只有一个 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

我们来看下 WebMvcAutoConfigurationAdapter 的 configurePathMatch 方法

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#configurePathMatch

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

    private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

		//省略部分方法以及成员变量 

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      if (this.mvcProperties.getPathmatch()
          .getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
        configurer.setPatternParser(pathPatternParser);
      }
      configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
      configurer.setUseRegisteredSuffixPatternMatch(
        this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
      this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
        String servletUrlMapping = dispatcherPath.getServletUrlMapping();
        if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
          UrlPathHelper urlPathHelper = new UrlPathHelper();
          urlPathHelper.setAlwaysUseFullPath(true);
          configurer.setUrlPathHelper(urlPathHelper);
        }
      });
    }
    //省略部分代码
}                                                                                            

configurePathMatch 方法中首先判断 配置属性 matchingStrategy 是否是 PATH_PATTERN_PARSER 如果未配置情况下 都是这个PATH_PATTERN_PARSER 所以 会进行设置setPatternParser 且设置值为 PathPatternParser

所以在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping

这里 RequestMappingHandlerMapping 所设置的处理类PatternParser 对应的实例为 PathPatternParser

public RequestMappingHandlerMapping requestMappingHandlerMapping(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

   RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
   mapping.setOrder(0);
   mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
   mapping.setContentNegotiationManager(contentNegotiationManager);
   mapping.setCorsConfigurations(getCorsConfigurations());

   PathMatchConfigurer pathConfig = getPathMatchConfigurer();
   if (pathConfig.getPatternParser() != null) {
      mapping.setPatternParser(pathConfig.getPatternParser());
   }
	 //省略部分代码
   return mapping;
}

RequestMappingHandlerMapping 是 Spring MVC 中的一个关键组件,用于映射请求到相应的处理方法(controller 方法)。其中的 patternParser 用于解析请求映射中的 URL 模式,以确定哪个处理方法应该处理特定的请求。

那后续将会使用 PathPatternParser 来处理请求路径
所以在不做配置的情况 默认都是使用的PathPatternParser 来解析请求路径

那解决的办法就出来了

  • 更换解析器 使用 ANT_PATH_MATCHER 具体配置方法如上

路径解析器修改后
新的调用就成了如下逻辑

protected String initLookupPath(HttpServletRequest request) {
	if (usesPathPatterns()) {
		//这个逻辑将不会走 因为patternParser 未设置为空
		request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
		RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
		String lookupPath = requestPath.pathWithinApplication().value();
		return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
	}
	else {
		//走的这段逻辑
		return getUrlPathHelper().resolveAndCacheLookupPath(request);
	}
}

org.springframework.web.util.UrlPathHelper#resolveAndCacheLookupPath

public String resolveAndCacheLookupPath(HttpServletRequest request) {
	String lookupPath = getLookupPathForRequest(request);
	request.setAttribute(PATH_ATTRIBUTE, lookupPath);
	return lookupPath;
}
public String getLookupPathForRequest(HttpServletRequest request) {
	String pathWithinApp = getPathWithinApplication(request);
	// Always use full path within current servlet context?
	if (this.alwaysUseFullPath || skipServletPathDetermination(request)) {
		return pathWithinApp;
	}
	// Else, use path within current servlet mapping if applicable
	String rest = getPathWithinServletMapping(request, pathWithinApp);
	if (StringUtils.hasLength(rest)) {
		return rest;
	}
	else {
		return pathWithinApp;
	}
}

首先获取应用内的路径

public String getPathWithinApplication(HttpServletRequest request) {
	String contextPath = getContextPath(request);
	String requestUri = getRequestUri(request);
	String path = getRemainingPath(requestUri, contextPath, true);
	if (path != null) {
		// Normal case: URI contains context path.
		return (StringUtils.hasText(path) ? path : "/");
	}
	else {
		return requestUri;
	}
}

其次获取servlet 映射路径

protected String getPathWithinServletMapping(HttpServletRequest request,
						 String pathWithinApp) {
	String servletPath = getServletPath(request);
	String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
	String path;

	// If the app container sanitized the servletPath, check against the sanitized version
	if (servletPath.contains(sanitizedPathWithinApp)) {
		path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
	}
	else {
		path = getRemainingPath(pathWithinApp, servletPath, false);
	}

	if (path != null) {
		// Normal case: URI contains servlet path.
		return path;
	}
	else {
		// Special case: URI is different from servlet path.
		String pathInfo = request.getPathInfo();
		if (pathInfo != null) {
			// Use path info if available. Indicates index page within a servlet mapping?
			// e.g. with index page: URI="/", servletPath="/index.html"
			return pathInfo;
		}
		if (!this.urlDecode) {
			// No path info... (not mapped by prefix, nor by extension, nor "/*")
			// For the default servlet mapping (i.e. "/"), urlDecode=false can
			// cause issues since getServletPath() returns a decoded path.
			// If decoding pathWithinApp yields a match just use pathWithinApp.
			path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
			if (path != null) {
				return pathWithinApp;
			}
		}
		// Otherwise, use the full servlet path.
		return servletPath;
	}
}

这样路径就被解析了 而不是另一种方式 直接从请求头获取 (如下)

public static RequestPath getParsedRequestPath(ServletRequest request) {
	RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
	Assert.notNull(path, () -> "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
	return path;
}

good day

你可能感兴趣的:(java,springboot,request,requestpath)