From ff2a911baa97921e018e6d5a7cd0f3dbaa55496e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 4 Mar 2016 16:01:35 -0500 Subject: [PATCH] Update MVC config to use CNM for static resources The MVC config now plugs the configured ContentNegotiationManager into resource request handling. Issue: SPR-13658 --- .../web/servlet/config/MvcNamespaceUtils.java | 19 +++++++++++++ .../config/ResourcesBeanDefinitionParser.java | 14 +++++++--- .../ViewResolversBeanDefinitionParser.java | 18 ++----------- .../annotation/ResourceHandlerRegistry.java | 10 +++++++ .../WebMvcConfigurationSupport.java | 3 ++- .../web/servlet/config/MvcNamespaceTests.java | 27 +++++++++++++------ ...MvcConfigurationSupportExtensionTests.java | 13 +++++++++ .../servlet/config/mvc-config-resources.xml | 1 + 8 files changed, 77 insertions(+), 28 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java index e668413688..fb787d1a0a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java @@ -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; + } + + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java index b462c66fb8..e8059186a0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java @@ -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)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java index cad1ad70fe..a91c937dfd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java @@ -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; - } - } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java index f9145d4ae0..a4c7c02a0c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java @@ -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 registrations = new ArrayList(); 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(); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 46b25b37be..967210565e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -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(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 8e3c347d79..a2a8b61596 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -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 beans = appContext.getBeansOfType(MappedInterceptor.class); + List> 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(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index 7fe30f7631..e30ed3a8dd 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -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 diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml index 82dd141b05..f4a216a3eb 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml @@ -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"> +