SpringMVC: Несоответствующее поведение отображения в зависимости от расширения URL
У меня есть конечная точка на основе 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;
}
Это работает, как и ожидалось для URL, как
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
С высоким логарифмом я вижу, что отображение ловится весной:
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 ответа
Это не ошибка, это особенность...
Как и предположили @axtavt и @rhinds, что-то мешает с типом контента. Браузеры отправляет правильный Accept: application/json
но весна игнорирует это и использует расширение 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);
}
}
Смотрите также Spring не игнорирует расширение файла для xml-config.
связанные с
Мне потребовался дополнительный шаг в моем особом случае. Я хотел бы добавить дополнительную информацию в замечательный комментарий @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;
}
}