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

时间:2014-03-11 14:55:09

标签: spring-mvc spring-4

我有一个基于RESTful spring的端点,可以将存储在db中的资源存储到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,就会失败。

经过一些调试后,如果我使用像

这样的网址,我确定请求甚至都没有映射
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作为扩展名 - bang:

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.xml和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();
    }

任何人都知道吗?

2 个答案:

答案 0 :(得分:14)

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

正如@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.

解决方案非常简单,因为您可以禁用此功能&#34;:

@Configuration
@EnableWebMvc
public class RestCommonsMvcConfig extends WebMvcConfigurerAdapter {

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

}

另请参阅Spring does not ignore file extension了解xml-config。

相关

答案 1 :(得分:1)

在我的特殊情况下我需要额外的一步。我想在@ 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;
    }
}