Spring MVC 開啟 REST 風格(<mvc:annotation-driven>)後,常遇到靜態資源、favicon、錯誤頁面等請求被 DispatcherServlet 攔截導致 404/405/406 錯誤。
訪問 /css/style.css 進入 Controller 方法,報 405 Method Not Allowed 或 404。
/ 或 /** 映射優先級高於靜態資源處理器。
<!-- 1. 精確映射 Controller,避免 /** -->
@RequestMapping("/api/users")
<!-- 2. 配置靜態資源處理優先 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:resources mapping="/favicon.ico" location="/favicon.ico"/>
<!-- 3. 預設 Servlet 兜底 -->
<mvc:default-servlet-handler/>
@GetMapping("/users/{id}") 與 @GetMapping("/users/profile") 衝突,profile 被當作 {id}。
<!-- 1. 使用正則限制路徑變量 -->
@GetMapping("/users/{id:\\d+}") // 僅匹配數字
@GetMapping("/users/profile") // 精確路徑優先定義
<!-- 2. 或調整映射順序 -->
@GetMapping("/users/profile")
@GetMapping("/users/{id}")
瀏覽器訪問 API 返回 406 Not Acceptable,或強制返回 HTML 而非 JSON。
預設 ContentNegotiationManager 依據 Accept 標頭決定返回類型,瀏覽器預設 text/html。
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value> <!-- 支援瀏覽器直接訪問 -->
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 或全域配置預設內容類型 -->
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(false)
.ignoreAcceptHeader(true) // 忽略 Accept,預設 JSON
.defaultMediaType(MediaType.APPLICATION_JSON);
}
}
表單提交 PUT/DELETE 方法,Controller 收到 POST 或 405 錯誤。
<!-- web.xml 配置 HiddenHttpMethodFilter(需在 Spring Security 之前)--> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 前端表單使用 _method 參數 --> <form method="POST" action="/api/users/1"> <input type="hidden" name="_method" value="DELETE"/> <button type="submit">刪除</button> </form>
/api/users 和 /api/users/ 導向不同處理器,或一個 404。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false); // 關閉尾部斜線自動匹配
}
}
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/")
.setCachePeriod(31536000)
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
.addTransformer(new GzipResourceTransformer());
registry.addResourceHandler("/favicon.ico")
.addResourceLocations("/favicon.ico");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable(); // 啟用容器 DefaultServlet
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.ignoreAcceptHeader(true)
.defaultMediaType(MediaType.APPLICATION_JSON);
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false);
}
@Override
public void extendMessageConverters(List> converters) {
// 確保 JSON 轉換器支援 text/html
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(c -> ((MappingJackson2HttpMessageConverter)c)
.setSupportedMediaTypes(Arrays.asList(
MediaType.APPLICATION_JSON_UTF8,
MediaType.TEXT_HTML)));
}
}
logging.level.org.springframework.web=DEBUG/mappings Actuator 端點curl -v -H "Accept: application/json" URL 測試內容協商FilterRegistrationBean.setOrder()