首页 > 解决方案 > Spring 请求映射逻辑是否应该根据 servletPath 的值映射到处理程序方法?

问题描述

抱歉,如果以前有人问过这个问题。我已经在 StackOverflow 和 Web 上搜索了 Spring 文档,但还没有找到答案。

我对 Spring 还很陌生,在调查我遇到的请求映射问题的过程中,我遇到了一些不寻常且出乎意料的(对我而言)行为,这会导致单个<url-pattern>@RequestMapping从 2 个不同的 URL 调用。我确信这是由于我缺乏理解,所以我希望有人可以确认它应该如何表现,并且最好指出我记录它的位置。我通过独立的 Servlet 容器使用 Spring Framework,而不是 SpringBoot。

以下示例说明了该行为。

考虑以下 web.xml 片段

<servlet>
  <servlet-name>TestSpringServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/test-spring-servlet-config.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>TestSpringServlet</servlet-name>
  <url-pattern>/test-servlet/*</url-pattern>
</servlet-mapping>

部署在以下上下文路径:

/apps

test-spring-servlet-config.xml 包含:

<beans>
  <mvc:annotation-driven />
  <bean id="TestController" class="org.example.TestSpringServletController" />
</beans>

TestSpringServletController 类定义为:

@Controller
public class TestSpringServletController
{
  @GetMapping("/test-servlet")
  public void testAll(HttpServletRequest request, HttpServletResponse response) throws IOException
  {
    response.getWriter().append("<h2>Spring Test Servlet - testAll()</h2>");
    response.getWriter().append("ContextPath: [").append(request.getContextPath()).append("]<br/>");
    response.getWriter().append("ServletPath: [").append(request.getServletPath()).append("]<br/>");
    response.getWriter().append("PathInfo: [").append(request.getPathInfo()).append("]");
  }


  @GetMapping("/test-servlet/{id}")
  public void testWithId(HttpServletRequest request, HttpServletResponse response, @PathVariable String id) throws IOException
  {
    response.getWriter().append("<h2>Spring Test Servlet - testWithId()</h2>");
    response.getWriter().append("ContextPath: [").append(request.getContextPath()).append("]<br/>");
    response.getWriter().append("ServletPath: [").append(request.getServletPath()).append("]<br/>");
    response.getWriter().append("PathInfo: [").append(request.getPathInfo()).append("]");
  }
}

访问: http://localhost:8084/apps/test-servlet/test-servlet 导致:

Spring Test Servlet - testAll()
ContextPath: [/apps]
ServletPath: [/test-servlet]
PathInfo: [/test-servlet]

正如预期的那样。

访问:

http://localhost:8084/apps/test-servlet/test-servlet/myid 结果是:

Spring Test Servlet - testWithId()
ContextPath: [/apps]
ServletPath: [/test-servlet]
PathInfo: [/test-servlet/myid]

也如预期。

但是,访问: http://localhost:8084/apps/test-servlet 会导致:

Spring Test Servlet - testAll()
ContextPath: [/apps]
ServletPath: [/test-servlet]
PathInfo: [null]

这不是我所期望的,我找不到这种行为的记录。我预计会出现 404 Not Found 错误。我假设正在发生的是,当 PathInfo 为空时,Spring Request Mapper 正在使用 ServletPath。但是,当 PathInfo 不为 null 时,仅使用 PathInfo 值,如下所示: http://localhost:8084/apps/test-servlet/myid 这会导致:

HTTP ERROR 404
Problem accessing /apps/test-servlet/myid. Reason: Not Found

我实际上确定我已经在某处读到 servletPath 不应被 servlet 用作请求的一部分,但目前找不到该特定参考。

将 testAll() 的 @GetMapping 更改为 "/" 和 testWithId() 的 "/{id}" 也没有预期的效果,因为访问: http://localhost:8084/apps/test-servlet 现在导致调用 testWithId() 而不是 testAll(),现在它的 URL 中需要一个尾随 / 才能像以前一样在没有尾随 / 的情况下运行。老实说,这也不是我所期望的,并且看起来是另一种情况,即 Spring 请求映射器使用 servletPath 代替 PathInfo(没有尾随空格为 null)。如果有人能阐明这一点,我也将不胜感激。

我发现避免该问题的一种方法是更改​​ url 模式(以及因此 servletPath)或更改 PathInfo 以使值不同。这似乎是一个奇怪的约束(并且将部署时间配置与编译时间配置紧密结合在一起),我在任何地方都没有提到过。

我们将不胜感激地收到任何人可以就这些行为提供的任何信息或指示。为这么冗长的问题道歉!

问候

====

快速更新以响应以下最初的评论:

将 web.xml 替换为:

public class WebAppBootstrap implements WebApplicationInitializer
{
  private static final String URI_TEST_SERVICE = "/test-servlet/*";
  private static final String NAME_TEST_SERVICE = "TestSpringServlet";

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException
  {
    AnnotationConfigWebApplicationContext testServletContext = new AnnotationConfigWebApplicationContext();
    testServletContext.register(TestServletConfig.class);
    ServletRegistration.Dynamic testDispatcher = servletContext.addServlet(NAME_TEST_SERVICE, new DispatcherServlet(testServletContext));
    testDispatcher.setLoadOnStartup(1);
    testDispatcher.addMapping(URI_TEST_SERVICE);
  }
}

test-spring-servlet-config.xml与:

@Configuration
@EnableWebMvc
public class TestServletConfig
{
  @Bean
  public TestSpringServletController testController()
  {
    return new TestSpringServletController();
  }
}

对观察到的行为没有任何影响。

标签: javaspringspring-mvcservlets

解决方案


查看 Spring 源代码后,似乎上述行为在某种程度上是设计使然(尽管上面的特定用例可能不是)。

在执行查找匹配的处理程序方法时确定要使用的路径时,将调用:UrlPathHelper.getLookupPathForRequest(HttpServletRequest request)

此方法首先检查是否alwaysUseFullPath设置为true。如果是,那么它同时使用 servlet 路径和路径信息作为查找路径。

如果alwaysUseFullPath设置为,false则它尝试仅使用路径信息(如您所料)构建查找路径。但是,如果找到的路径是一个空字符串,那么它会回退到同时使用 servlet 路径和路径信息,即好像alwaysUseFullPath设置为true.

因此,结果是 URI/apps/test-servlet/test-servlet/apps/test-servlet结果都是 的查找路径,/test-servlet因此都将与 的@GetMapping值匹配testAll()

很遗憾没有neverUseFullPath设置,因为我认为我观察到的行为不是可取的,即使它可能不太可能(尽管如果另一个团队负责编码 servlet 和部署 servlet,那么也许你可以在 URI 中获得重复项)。


推荐阅读