问题描述
很抱歉,是否曾经有人问过这个问题。我已经在StackOverflow和整个Web上搜索过Spring文档,但没有找到答案。
我对Spring还是很陌生,在调查我遇到的请求映射问题的过程中,我遇到了一些异常和意外的行为(对我而言),这些行为导致了一个<url-pattern>
和{{ 1}}从2个不同的URL中调用。
我敢肯定,这是由于我缺乏了解,所以我希望有人可以确认它的行为方式,最好将其指向记录的地方。我通过独立的Servlet容器而不是SpringBoot使用Spring Framework。
下面的示例说明了行为。
考虑以下web.xml代码段
@RequestMapping
部署在以下上下文路径中:
<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>
test-spring-servlet-config.xml包含:
/apps
TestSpringServletController类定义为:
<beans>
<mvc:annotation-driven />
<bean id="TestController" class="org.example.TestSpringServletController" />
</beans>
访问:
@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
这不是我所期望的,我找不到记录的行为。我期望出现404 Not Found错误。我假设发生的事情是,当PathInfo为null时,Spring Request Mapper使用ServletPath代替。但是,当PathInfo不为null时,则仅使用PathInfo值,如下所示:
Spring Test Servlet - testAll()
contextpath: [/apps]
ServletPath: [/test-servlet]
PathInfo: [null]
结果是:
http://localhost:8084/apps/test-servlet/myid
我确实确定我已经读过某个地方servlet不应将servletPath用作请求的一部分,但是此刻无法找到该特定引用。
将testAll()的@GetMapping更改为“ /”,将testWithId()的@GetMapping更改为“ / {id}”也不具有预期的效果,因为访问:
HTTP ERROR 404
Problem accessing /apps/test-servlet/myid. Reason: Not Found
现在导致对testWithId()而不是testAll()的调用,该调用现在需要在URL中使用尾随/才能像以前一样操作而没有尾随/。老实说,这也不是我所期望的,并且看起来是另一种情况,其中Spring请求映射器使用servletPath代替PathInfo(在没有尾随空格的情况下为null)。如果有人可以阐明这一点,我也将不胜感激。
我发现可以避免此问题的一种方法是更改url-pattern(从而更改servletPath)或更改PathInfo以使值不同。这似乎是一个奇怪的约束(并将部署时间配置与编译时间配置紧密结合在一起),我在任何地方都没有提到。
任何人都可以提供的关于任何一种行为的信息或指针,将不胜感激。道歉这么长的问题!
致谢
====
将web.xml替换为:
http://localhost:8084/apps/test-servlet
和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
对观察到的行为没有任何影响。
解决方法
看过Spring源代码后,似乎上面的行为是设计使然的(尽管上面的特定用例可能并非如此)。
确定执行匹配处理程序方法的查找时要使用的路径时,将调用:UrlPathHelper.getLookupPathForRequest(HttpServletRequest request)
此方法首先检查alwaysUseFullPath
是否设置为true
。如果是,那么它将同时使用servlet路径和路径信息作为查找路径。
如果将alwaysUseFullPath
设置为false
,则它将尝试仅使用路径信息来构造查找路径(如您期望的那样)。但是,如果找到的路径为空字符串,则它会退回到同时使用servlet路径和路径信息,即好像alwaysUseFullPath
设置为true
。
因此,结果是URI的/apps/test-servlet/test-servlet
和/apps/test-servlet
都导致了/test-servlet
的查找路径,因此都将与{{ 1}}。
可惜没有@GetMapping
设置,因为我认为我观察到的行为不是可取的,即使这种可能性不太可能(尽管由不同的团队负责编写servlet并部署servlet,那么也许可以在URI中获得重复项。