SpringMVC:取决于 url 扩展的不一致映射行为

Posted

技术标签:

【中文标题】SpringMVC:取决于 url 扩展的不一致映射行为【英文标题】:SpringMVC: Inconsistent mapping behavior depending on url extension 【发布时间】:2014-04-15 06:45:12 【问题描述】:

我有一个基于 RESTful spring 的端点来获取存储在数据库中的资产到 javascript 编辑器。相关部分归结为:

@RestController
@RequestMapping(ThemeEndpoint.ENDPOINT_NAME)
public class ThemeEndpoint 

public static final String ENDPOINT_NAME = "/themes"; 

    @RequestMapping(value="/id/css/assetName:.*", method=RequestMethod.GET)
    public Asset getCssItem(
        @PathVariable("id") ThemeId id, 
        @PathVariable("assetName") String name) 
    CssThemeAsset themeAsset = themeService.getCssAsset(
                id, ThemeAssetId.fromString(name));
    Asset asset = new Asset();
    asset.name = themeAsset.getName();
    asset.text = themeAsset.getContent();
    return asset;

这对像这样的网址按预期工作

http://localhost:8080/app-url/rest/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.less

但一旦我将扩展名更改为.css,就会失败。

经过一些调试,我很确定如果我使用像这样的 url,请求甚至没有被映射

http://localhost:8080/app-url/rest/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.css

如果日志级别很高,我可以看到映射被 spring 捕获:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 
    - Mapped "[/themes/id/css/assetName:.*],methods=[GET],params=[],headers=[],
                consumes=[],produces=[application/json],custom=[]" 
      onto public xxx.endpoint.ThemeEndpoint$Asset
          xxx.endpoint.ThemeEndpoint.getCssItem(
              net.lacho.svc.themes.api.ThemeId,java.lang.String)   

并且使用非 .css 扩展名调用控制器:

Found 1 matching mapping(s) for [/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.less]
  : [[/themes/id/css/assetName:.*],methods=[GET],params=[],headers=[],
        consumes=[],produces=[application/json],custom=[]]

但只要我使用 .css 作为扩展名 - 砰:

Looking up handler method for path /themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/test.css
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - 
    Resolving exception from handler [null]:  
org.springframework.web.HttpMediaTypeNotAcceptableException: 
    Could not find acceptable representation

web.xmland MVC-Config 按要求:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0"
    metadata-complete="false">

<welcome-file-list>
    <welcome-file>index</welcome-file>
</welcome-file-list>

</web-app>

WebApplicationInitializer:

package net.lacho.opcenter.bootstrap;


public class WebApplicationBootstrapper implements WebApplicationInitializer 


    @Override
    public void onStartup(ServletContext container) throws ServletException 

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.setConfigLocation(ApplicationConfig.class.getName());

        container.addListener(new ContextLoaderListener(rootContext));

        registerRestDispatcher(container);
        registerDefaultDispatcher(container);

        container.addFilter("CharacterEncodingFilter", UTF8EncodingFilter.class).addMappingForUrlPatterns(null,  true,  "/*");
        container.addFilter("headSupportFilter", HeadSupportFilter.class).addMappingForUrlPatterns(null,  true,  "/*");

        DelegatingFilterProxy shallowFrontendContextFilterProxy = new DelegatingFilterProxy("shallowFrontendContextProviderLocalFilter");
        shallowFrontendContextFilterProxy.setTargetFilterLifecycle(true);
        FilterRegistration.Dynamic shallowFrontendFilter = container.addFilter("ShallowFrontendContextFilter", shallowFrontendContextFilterProxy);
        shallowFrontendFilter.setInitParameter("ignoreNullClient", "true");
        shallowFrontendFilter.addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("FrontendContextFilter", new DelegatingFilterProxy("frontendContextProviderLocalFilter"))
            .addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("hiddenHttpMethodFilter", new HiddenHttpMethodFilter()).addMappingForUrlPatterns(null,  true,  "/rest/*");;
    

    public void registerRestDispatcher(ServletContext container) 
        AnnotationConfigWebApplicationContext restDispatcherContext = new AnnotationConfigWebApplicationContext();
        restDispatcherContext.register(RestCommonsMvcConfig.class);

        ServletRegistration.Dynamic restDispatcher = container.addServlet("rest-dispatcher", new DispatcherServlet(restDispatcherContext));
        restDispatcher.setLoadOnStartup(1);
        restDispatcher.addMapping("/rest/*");
    

    public void registerDefaultDispatcher(ServletContext container) 
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(MvcConfig.class);

        ServletRegistration.Dynamic dispatcher = container.addServlet("backend-dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*", "/index");
    


MVC 配置:

package net.lacho.opcenter.bootstrap;



@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "net.lacho.opcenter.ui" )
public class MvcConfig extends WebMvcConfigurerAdapter 

    ... many lines removed, containing interceptors and velocity-config ...

@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) 
        registry
            .addResourceHandler("/_r/_s/**")
            .addResourceLocations("classpath:/static-resources/")
            .setCachePeriod(365 * 86400);
        registry
            .addResourceHandler("/_r/_d/**")
            .addResourceLocations("classpath:/static-uncached-resources/");
    

    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) 
        configurer.enable();
    

有人知道吗?

【问题讨论】:

您的 web.xml 和 mvc 映射配置是什么样的?猜测有东西将 *.css 识别为资源并将其映射到资源 servlet 如何将Assert 转换为HTTP 响应?显示您的自定义HttpMessageConverter,如果您使用它。 @rhinds 添加了请求的文件。 @axtavt 没有自定义转换器。我通过 maven 导入 Jackson 2,它被 spring 拾取(正如我所说,只要我的 url 不以 .css 结尾,控制器就会被调用并且编组到 JSON 工作)。 【参考方案1】:

这不是一个错误,这是一个功能......

正如@axtavt 和@rhinds 所假设的那样,内容类型有些混乱。浏览器发送了一个正确的Accept: application/json,但spring 忽略了这一点并使用了url 的扩展名(aarrgh)。来自文档:

16.16.4 配置内容协商

You can configure how Spring MVC determines the requested media types from the client for
request mapping as well as for content negotiation purposes. The available options are to 
check the file extension in the request URI, the "Accept" header, a request parameter, as 
well as to fall back on a default content type. By default, file extension in the request 
URI is checked first and the "Accept" header is checked next.

解决方案非常简单,您可以禁用此“功能”:

@Configuration
@EnableWebMvc
public class RestCommonsMvcConfig extends WebMvcConfigurerAdapter 

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) 
        configurer.favorPathExtension(false);
    


有关 xml 配置,另请参阅 Spring does not ignore file extension。

相关

Spring MVC @PathVariable with dot (.) is getting truncated

【讨论】:

【参考方案2】:

在我的特殊情况下,我需要一个额外的步骤。我想在@dirk-lachowski 的精彩评论中添加额外信息,以防其他人需要。就我而言,我正在从 WebMvcConfigurationSupport 类导入扩展

@Configuration
@EnableWebMvc
@Import( SearchWebMvcConfigurationSupport.class )
public class SearchSpringConfiguration extends WebMvcConfigurerAdapter 
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) 
        configurer.favorPathExtension(false);
    

如果您需要覆盖requestMappingHandlerMapping 方法,那么您还必须添加m.setUseSuffixPatternMatch(false);

@Configuration
public class SearchWebMvcConfigurationSupport extends WebMvcConfigurationSupport 
    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() 
        RequestMappingHandlerMapping m = super.requestMappingHandlerMapping();
        m.setAlwaysUseFullPath(true); // This makes your endpoints receive full path, including servlet mapping (you probably will not need this)
        m.setUseSuffixPatternMatch(false);
        return m;
    

【讨论】:

以上是关于SpringMVC:取决于 url 扩展的不一致映射行为的主要内容,如果未能解决你的问题,请参考以下文章

Spring映射器适配器解析器

springMVC-配置

springmvc核心流程

SpringMVC的流程

springMVC工作过程

SpringMVC工作原理