2023年4月25日 星期二

Java EE 使用 Spring 將非根目錄的 url pattern 送給 DispatcherServlet 時要注意的事情 - 使用 RequestMappingHandlerMapping 的 alwaysUseFullPath 設定

在 Java EE 使用 Spring 時,雖然大部份情況是在專案的 web.xml 
裡設定

<url-pattern>/</url-pattern>

把所有的 url request 送進 DispatcherServlet 去處理,
但有時我們只想把某個 url pattern 之下的 url 送給 DispatcherServlet 處理 (例如你可能有多個 DispatcherServlet,或是想在沒使用 Spring 的專案加進有使用 Spring 的 API url),
這時我們可能會這樣設,例如:

<url-pattern>/api/*</url-pattern>

此例代表符合 /api/* 的 url 才會交給 DispatcherServlet 處理,如下範例:

<servlet>
		<servlet-name>dispatcher</servlet-name>
	    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	    <init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/mvc-config.xml</param-value>
		</init-param>
	    <load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
	    <servlet-name>dispatcher</servlet-name> 
	    <url-pattern>/api/*</url-pattern>
	</servlet-mapping>
這時你可能會發現 /api/test 的 url request 無法進入到以下 Controller 中而回應 404 No mapping for GET /api/test:
package test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SpringControllerTest {
	
	@RequestMapping(value="/api/test" , method = {RequestMethod.GET})
	public @ResponseBody String login(HttpServletRequest request, HttpServletResponse httpResp) throws Exception{
		System.out.println("Hello");
		return "Hello";
	}
}

這是因為根據 Spring MVC 的 Handler mappings 規則

alwaysUseFullPath
if this property is set to true, Spring will use the full path within the current servlet context to find an appropriate handler. If this property is set to false (the default), the path within the current servlet mapping will be used. For example, if a servlet is mapped using /testing/* and the alwaysUseFullPath property is set to true, /testing/viewPage.html would be used, whereas if the property is set to false, /viewPage.html would be used.

,當 alwaysUseFullPath 參數值是預設的 false 時,
DispatcherServlet 會將 url request 扣掉 web.xml 的 <url-pattern> 所設值 (此例就是 /api,/api/test 扣掉 /api 後是 /test),
然後才會跟 @RequestMapping 所設的 value 值做比對,
也就是此時 @RequestMapping 的 value 需要設成 /test 才會比對成功。

如果要比對 @RequestMapping value="/api/test" 成功的話, url request 需要是
/api/api/test 才行。

上例是 alwaysUseFullPath = false (default) 的情況,
如果這次我們把 alwaysUseFullPath 設成 true 的話,
DispatcherServlet 就不會將 url request 扣掉 web.xml 設定的 <url-pattern> 值,
這時 /api/test 的 url request 就可以被比對到而進入 Controller 中了,
設定 alwaysUseFullPath=true 的方式為在 spring config 檔中加下如下設定:

<bean id="requestMappingHandlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
       <property name="alwaysUseFullPath" value="true"/>
    </bean>  

參考資料:

  1. 16. Web MVC framework - 16.4 Handler mappings - alwaysUseFullPath