【问题标题】:Shoud the Spring request mapping logic map to a handler method based upon the value of servletPath?Spring requestmapping 逻辑是否应该根据 servlet Path 的值映射到处理程序方法?
【发布时间】:2020-12-08 04:32:23
【问题描述】:

如果之前有人问过这个问题,我们深表歉意。我已经搜索了 Spring 文档、StackOverflow 和整个 Web,但没有找到答案。

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

以下示例说明了该行为。

考虑以下 web.xml sn-p

<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 用作请求的一部分,但目前找不到该特定引用。

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

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

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

问候

====

响应以下初始 cmets 的快速更新:

将 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();
  }
}

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

【问题讨论】:

  • 这是针对新应用程序还是旧应用程序?现代风格是消除所有web.xml业务,使用Spring Boot。 (无论哪种情况,您都应该尽量避免直接使用HttpServletR*;Spring 的重点是 MVC 基础架构为您处理所有映射和绑定。)
  • 这是一个新的应用程序。虽然实际应用程序仍然是 Spring Framework 而不是 Spring Boot,但它不使用 HttpServletR* 或 web.xml。上面的例子只是为了说明我在一个简单的测试 Servlet 中用最少的代码看到的问题,并提供对调试输出的请求 URL 字段的轻松访问。
  • 从示例代码中删除了日志记录行,使其更简洁,因为响应输出中的相同细节
  • 如果您的“实际应用程序”不使用web.xml,那么该示例已被删除,以至于无法帮助您调试路径问题。
  • 根据上述 cmets,我已经删除了 web.xml 和 Spring xml 配置,并将它们替换为基于代码的配置替代方案(相关代码添加到问题文本中)。这对观察到的行为没有影响。

标签: java spring spring-mvc servlets


【解决方案1】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-11
    • 1970-01-01
    • 2017-04-24
    相关资源
    最近更新 更多