Update MVC config to use CNM for static resources

The MVC config now plugs the configured ContentNegotiationManager into
resource request handling.

Issue: SPR-13658
This commit is contained in:
Rossen Stoyanchev
2016-03-04 16:01:35 -05:00
parent f162256906
commit ff2a911baa
8 changed files with 77 additions and 28 deletions

View File

@@ -178,4 +178,23 @@ abstract class MvcNamespaceUtils {
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}
/**
* Find the {@code ContentNegotiationManager} bean created by or registered
* with the {@code annotation-driven} element.
* @return a bean definition, bean reference, or null.
*/
public static Object getContentNegotiationManager(ParserContext context) {
String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
if (context.getRegistry().containsBeanDefinition(name)) {
BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
}
name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
if (context.getRegistry().containsBeanDefinition(name)) {
return new RuntimeBeanReference(name);
}
return null;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit;
import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -165,17 +166,19 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", locations);
MutablePropertyValues values = resourceHandlerDef.getPropertyValues();
values.add("locations", locations);
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
values.add("cacheSeconds", cacheSeconds);
}
Element cacheControlElement = DomUtils.getChildElementByTagName(element, "cache-control");
if (cacheControlElement != null) {
CacheControl cacheControl = parseCacheControl(cacheControlElement);
resourceHandlerDef.getPropertyValues().add("cacheControl", cacheControl);
values.add("cacheControl", cacheControl);
}
Element resourceChainElement = DomUtils.getChildElementByTagName(element, "resource-chain");
@@ -183,6 +186,11 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
parseResourceChain(resourceHandlerDef, parserContext, resourceChainElement, source);
}
Object manager = MvcNamespaceUtils.getContentNegotiationManager(parserContext);
if (manager != null) {
values.add("contentNegotiationManager", manager);
}
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
@@ -192,24 +191,11 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
if (resolverElement.hasAttribute("use-not-acceptable")) {
values.add("useNotAcceptableStatusCode", resolverElement.getAttribute("use-not-acceptable"));
}
Object manager = getContentNegotiationManager(context);
Object manager = MvcNamespaceUtils.getContentNegotiationManager(context);
if (manager != null) {
values.add("contentNegotiationManager", manager);
}
return beanDef;
}
private Object getContentNegotiationManager(ParserContext context) {
String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
if (context.getRegistry().containsBeanDefinition(name)) {
BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
}
name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
if (context.getRegistry().containsBeanDefinition(name)) {
return new RuntimeBeanReference(name);
}
return null;
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
@@ -55,15 +56,23 @@ public class ResourceHandlerRegistry {
private final ApplicationContext appContext;
private final ContentNegotiationManager contentNegotiationManager;
private final List<ResourceHandlerRegistration> registrations = new ArrayList<ResourceHandlerRegistration>();
private int order = Integer.MAX_VALUE -1;
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext) {
this(applicationContext, servletContext, null);
}
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
ContentNegotiationManager contentNegotiationManager) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.appContext = applicationContext;
this.servletContext = servletContext;
this.contentNegotiationManager = contentNegotiationManager;
}
@@ -113,6 +122,7 @@ public class ResourceHandlerRegistry {
ResourceHttpRequestHandler handler = registration.getRequestHandler();
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.appContext);
handler.setContentNegotiationManager(this.contentNegotiationManager);
try {
handler.afterPropertiesSet();
}

View File

@@ -407,7 +407,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext);
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, mvcContentNegotiationManager());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();

View File

@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull;
@@ -145,6 +146,8 @@ import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
import org.springframework.web.util.UrlPathHelper;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
/**
@@ -343,17 +346,21 @@ public class MvcNamespaceTests {
@Test
public void testResources() throws Exception {
loadBeanDefinitions("mvc-config-resources.xml", 10);
loadBeanDefinitions("mvc-config-resources.xml", 20);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
ResourceHttpRequestHandler handler = appContext.getBean(ResourceHttpRequestHandler.class);
assertNotNull(handler);
assertSame(manager, handler.getContentNegotiationManager());
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, mapping.getOrder());
SimpleUrlHandlerMapping resourceMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(resourceMapping);
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, resourceMapping.getOrder());
BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
assertNotNull(beanNameMapping);
@@ -362,15 +369,19 @@ public class MvcNamespaceTests {
ResourceUrlProvider urlProvider = appContext.getBean(ResourceUrlProvider.class);
assertNotNull(urlProvider);
MappedInterceptor mappedInterceptor = appContext.getBean(MappedInterceptor.class);
assertNotNull(urlProvider);
assertEquals(ResourceUrlProviderExposingInterceptor.class, mappedInterceptor.getInterceptor().getClass());
Map<String, MappedInterceptor> beans = appContext.getBeansOfType(MappedInterceptor.class);
List<Class<?>> interceptors = beans.values().stream()
.map(mappedInterceptor -> mappedInterceptor.getInterceptor().getClass())
.collect(Collectors.toList());
assertThat(interceptors, containsInAnyOrder(ConversionServiceExposingInterceptor.class,
ResourceUrlProviderExposingInterceptor.class));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/resources/foo.css");
request.setMethod("GET");
HandlerExecutionChain chain = mapping.getHandler(request);
HandlerExecutionChain chain = resourceMapping.getHandler(request);
assertNotNull(chain);
assertTrue(chain.getHandler() instanceof ResourceHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();

View File

@@ -62,16 +62,19 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@@ -84,6 +87,7 @@ import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.springframework.http.MediaType.APPLICATION_ATOM_XML;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_XML;
@@ -253,6 +257,15 @@ public class WebMvcConfigurationSupportExtensionTests {
request.setRequestURI("/foo");
request.setParameter("f", "json");
assertEquals(Collections.singletonList(APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
request.setRequestURI("/resources/foo.gif");
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.config.resourceHandlerMapping();
handlerMapping.setApplicationContext(this.context);
HandlerExecutionChain chain = handlerMapping.getHandler(request);
assertNotNull(chain);
ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) chain.getHandler();
assertNotNull(handler);
assertSame(manager, handler.getContentNegotiationManager());
}
@Test

View File

@@ -5,6 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" />
</beans>