From 028e15faa33888673bfc7b55eaa65eb93f7bca0c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 20 Jul 2012 21:12:13 -0400 Subject: [PATCH] Add options to configure content negotiation The MVC Java config and the MVC namespace now support options to configure content negotiation. By default both support checking path extensions first and the "Accept" header second. For path extensions .json, .xml, .atom, and .rss are recognized out of the box if the Jackson, JAXB2, or Rome libraries are available. The ServletContext and the Java Activation Framework may be used as fallback options for path extension lookups. Issue: SPR-8420 --- .../web/accept/ContentNegotiationManager.java | 21 +- .../AnnotationDrivenBeanDefinitionParser.java | 99 ++++++--- .../ContentNegotiationConfigurer.java | 195 ++++++++++++++++++ .../DelegatingWebMvcConfiguration.java | 5 + .../annotation/InterceptorRegistry.java | 19 +- .../WebMvcConfigurationSupport.java | 52 ++++- .../config/annotation/WebMvcConfigurer.java | 5 + .../annotation/WebMvcConfigurerAdapter.java | 7 + .../annotation/WebMvcConfigurerComposite.java | 6 + .../web/servlet/config/spring-mvc-3.2.xsd | 16 ++ .../web/servlet/config/MvcNamespaceTests.java | 40 +++- .../ContentNegotiationConfigurerTests.java | 112 ++++++++++ .../DelegatingWebMvcConfigurationTests.java | 4 + .../WebMvcConfigurationSupportTests.java | 29 ++- ...mvc-config-content-negotiation-manager.xml | 29 +++ src/dist/changelog.txt | 1 + src/reference/docbook/mvc.xml | 48 +++++ 17 files changed, 638 insertions(+), 50 deletions(-) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurerTests.java create mode 100644 spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-content-negotiation-manager.xml diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java index 285f547c57a..f87d5495f85 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java @@ -30,10 +30,19 @@ import org.springframework.web.context.request.NativeWebRequest; /** * This class is used to determine the requested {@linkplain MediaType media types} - * in a request by delegating to a list of {@link ContentNegotiationStrategy} instances. + * of a request by delegating to a list of ContentNegotiationStrategy instances. + * The strategies must be provided at instantiation or alternatively if using + * the default constructor, an instance of {@link HeaderContentNegotiationStrategy} + * will be configured by default. * - *

It may also be used to determine the extensions associated with a MediaType by - * delegating to a list of {@link MediaTypeFileExtensionResolver} instances. + *

This class may also be used to look up file extensions associated with a + * MediaType. This is done by consulting the list of configured + * {@link MediaTypeFileExtensionResolver} instances. Note that some + * ContentNegotiationStrategy implementations also implement + * MediaTypeFileExtensionResolver and the class constructor accepting the former + * will also detect if they implement the latter. If you need to register additional + * resolvers, you can use the method + * {@link #addFileExtensionResolvers(MediaTypeFileExtensionResolver...)}. * * @author Rossen Stoyanchev * @since 3.2 @@ -50,6 +59,7 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me * Create an instance with the given ContentNegotiationStrategy instances. *

Each instance is checked to see if it is also an implementation of * MediaTypeFileExtensionResolver, and if so it is registered as such. + * @param strategies one more more ContentNegotiationStrategy instances */ public ContentNegotiationManager(ContentNegotiationStrategy... strategies) { Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); @@ -70,6 +80,11 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me /** * Add MediaTypeFileExtensionResolver instances. + *

Note that some {@link ContentNegotiationStrategy} implementations also + * implement {@link MediaTypeFileExtensionResolver} and the class constructor + * accepting the former will also detect implementations of the latter. Therefore + * you only need to use this method to register additional instances. + * @param one more resolvers */ public void addFileExtensionResolvers(MediaTypeFileExtensionResolver... resolvers) { this.fileExtensionResolvers.addAll(Arrays.asList(resolvers)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index cf0dafb584a..89ba05baf6c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -16,7 +16,10 @@ package org.springframework.web.servlet.config; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -29,6 +32,7 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionServiceFactoryBean; +import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; @@ -44,6 +48,9 @@ import org.springframework.util.ClassUtils; import org.springframework.util.xml.DomUtils; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.HttpRequestHandler; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; +import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; @@ -102,9 +109,10 @@ import org.w3c.dom.Element; * * *

Both the {@link RequestMappingHandlerAdapter} and the - * {@link ExceptionHandlerExceptionResolver} are configured with default - * instances of the following kind, unless custom instances are provided: + * {@link ExceptionHandlerExceptionResolver} are configured with instances of + * the following by default: *