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
This commit is contained in:
@@ -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;
|
||||
* </ul>
|
||||
*
|
||||
* <p>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:
|
||||
* <ul>
|
||||
* <li>A {@link ContentNegotiationManager}
|
||||
* <li>A {@link DefaultFormattingConversionService}
|
||||
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
|
||||
* available on the classpath
|
||||
@@ -143,11 +151,14 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
|
||||
parserContext.pushContainingComponent(compDefinition);
|
||||
|
||||
RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
|
||||
methodMappingDef.setSource(source);
|
||||
methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodMappingDef.getPropertyValues().add("order", 0);
|
||||
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(methodMappingDef);
|
||||
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
|
||||
|
||||
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
|
||||
handlerMappingDef.setSource(source);
|
||||
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
handlerMappingDef.getPropertyValues().add("order", 0);
|
||||
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
|
||||
|
||||
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
|
||||
RuntimeBeanReference validator = getValidator(element, source, parserContext);
|
||||
@@ -164,22 +175,23 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
|
||||
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
|
||||
|
||||
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
|
||||
methodAdapterDef.setSource(source);
|
||||
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
|
||||
methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
|
||||
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
|
||||
handlerAdapterDef.setSource(source);
|
||||
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
|
||||
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
|
||||
if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
|
||||
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
|
||||
methodAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
|
||||
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
|
||||
}
|
||||
if (argumentResolvers != null) {
|
||||
methodAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
|
||||
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
|
||||
}
|
||||
if (returnValueHandlers != null) {
|
||||
methodAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
|
||||
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
|
||||
}
|
||||
String methodAdapterName = parserContext.getReaderContext().registerWithGeneratedName(methodAdapterDef);
|
||||
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
|
||||
|
||||
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
|
||||
csInterceptorDef.setSource(source);
|
||||
@@ -191,13 +203,14 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
|
||||
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
|
||||
|
||||
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
|
||||
methodExceptionResolver.setSource(source);
|
||||
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
|
||||
methodExceptionResolver.getPropertyValues().add("order", 0);
|
||||
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
|
||||
exceptionHandlerExceptionResolver.setSource(source);
|
||||
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
|
||||
String methodExceptionResolverName =
|
||||
parserContext.getReaderContext().registerWithGeneratedName(methodExceptionResolver);
|
||||
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
|
||||
|
||||
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
|
||||
responseStatusExceptionResolver.setSource(source);
|
||||
@@ -213,9 +226,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
String defaultExceptionResolverName =
|
||||
parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
|
||||
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
|
||||
@@ -261,6 +274,42 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeBeanReference getContentNegotiationManager(Element element, Object source, ParserContext parserContext) {
|
||||
RuntimeBeanReference contentNegotiationManagerRef;
|
||||
if (element.hasAttribute("content-negotiation-manager")) {
|
||||
contentNegotiationManagerRef = new RuntimeBeanReference(element.getAttribute("content-negotiation-manager"));
|
||||
}
|
||||
else {
|
||||
RootBeanDefinition managerDef = new RootBeanDefinition(ContentNegotiationManager.class);
|
||||
managerDef.setSource(source);
|
||||
managerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
PathExtensionContentNegotiationStrategy strategy1 = new PathExtensionContentNegotiationStrategy(getDefaultMediaTypes());
|
||||
HeaderContentNegotiationStrategy strategy2 = new HeaderContentNegotiationStrategy();
|
||||
managerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, Arrays.asList(strategy1,strategy2));
|
||||
|
||||
String beanName = "mvcContentNegotiationManager";
|
||||
parserContext.getReaderContext().getRegistry().registerBeanDefinition(beanName , managerDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(managerDef, beanName));
|
||||
contentNegotiationManagerRef = new RuntimeBeanReference(beanName);
|
||||
}
|
||||
return contentNegotiationManagerRef;
|
||||
}
|
||||
|
||||
private Map<String, MediaType> getDefaultMediaTypes() {
|
||||
Map<String, MediaType> map = new HashMap<String, MediaType>();
|
||||
if (romePresent) {
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
|
||||
if (element.hasAttribute("message-codes-resolver")) {
|
||||
return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.FixedContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||
|
||||
/**
|
||||
* Helps with configuring a {@link ContentNegotiationManager}.
|
||||
*
|
||||
* <p>By default the extension of the request path extension is checked first and
|
||||
* the {@code Accept} is checked second. The path extension check will perform a
|
||||
* look up in the media types configured via {@link #setMediaTypes(Map)} and
|
||||
* will also fall back to {@link ServletContext} and the Java Activation Framework
|
||||
* (if present).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ContentNegotiationConfigurer {
|
||||
|
||||
private boolean favorPathExtension = true;
|
||||
|
||||
private boolean favorParameter = false;
|
||||
|
||||
private boolean ignoreAcceptHeader = false;
|
||||
|
||||
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
|
||||
|
||||
private Boolean useJaf;
|
||||
|
||||
private String parameterName;
|
||||
|
||||
private MediaType defaultContentType;
|
||||
|
||||
/**
|
||||
* Indicate whether the extension of the request path should be used to determine
|
||||
* the requested media type with the <em>highest priority</em>.
|
||||
* <p>By default this value is set to {@code true} in which case a request
|
||||
* for {@code /hotels.pdf} will be interpreted as a request for
|
||||
* {@code "application/pdf"} regardless of the {@code Accept} header.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setFavorPathExtension(boolean favorPathExtension) {
|
||||
this.favorPathExtension = favorPathExtension;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mappings from file extensions to media types.
|
||||
* <p>If this property is not set, the Java Action Framework, if available, may
|
||||
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer addMediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
this.mediaTypes.put(extension, entry.getValue());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mappings from file extensions to media types replacing any previous mappings.
|
||||
* <p>If this property is not set, the Java Action Framework, if available, may
|
||||
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
this.mediaTypes.clear();
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
this.mediaTypes.put(extension, entry.getValue());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether to use the Java Activation Framework as a fallback option
|
||||
* to map from file extensions to media types. This is used only when
|
||||
* {@link #setFavorPathExtension(boolean)} is set to {@code true}.
|
||||
* <p>The default value is {@code true}.
|
||||
* @see #parameterName
|
||||
* @see #setMediaTypes(Map)
|
||||
*/
|
||||
public ContentNegotiationConfigurer setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether a request parameter should be used to determine the
|
||||
* requested media type with the <em>2nd highest priority</em>, i.e.
|
||||
* after path extensions but before the {@code Accept} header.
|
||||
* <p>The default value is {@code false}. If set to to {@code true}, a request
|
||||
* for {@code /hotels?format=pdf} will be interpreted as a request for
|
||||
* {@code "application/pdf"} regardless of the {@code Accept} header.
|
||||
* <p>To use this option effectively you must also configure the MediaType
|
||||
* type mappings via {@link #setMediaTypes(Map)}.
|
||||
* @see #setParameterName(String)
|
||||
*/
|
||||
public ContentNegotiationConfigurer setFavorParameter(boolean favorParameter) {
|
||||
this.favorParameter = favorParameter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type
|
||||
* if the {@link #setFavorParameter} property is {@code true}.
|
||||
* <p>The default parameter name is {@code "format"}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the HTTP {@code Accept} header should be ignored altogether.
|
||||
* If set the {@code Accept} header is checked at the
|
||||
* <em>3rd highest priority</em>, i.e. after the request path extension and
|
||||
* possibly a request parameter if configured.
|
||||
* <p>By default this value is set to {@code false}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
|
||||
this.ignoreAcceptHeader = ignoreAcceptHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default content type.
|
||||
* <p>This content type will be used when neither the request path extension,
|
||||
* nor a request parameter, nor the {@code Accept} header could help determine
|
||||
* the requested content type.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setDefaultContentType(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the configured {@link ContentNegotiationManager} instance
|
||||
*/
|
||||
protected ContentNegotiationManager getContentNegotiationManager() {
|
||||
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
|
||||
if (this.favorPathExtension) {
|
||||
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
|
||||
if (this.useJaf != null) {
|
||||
strategy.setUseJaf(this.useJaf);
|
||||
}
|
||||
strategies.add(strategy);
|
||||
}
|
||||
if (this.favorParameter) {
|
||||
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
|
||||
strategy.setParameterName(this.parameterName);
|
||||
strategies.add(strategy);
|
||||
}
|
||||
if (!this.ignoreAcceptHeader) {
|
||||
strategies.add(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
if (this.defaultContentType != null) {
|
||||
strategies.add(new FixedContentNegotiationStrategy(this.defaultContentType));
|
||||
}
|
||||
ContentNegotiationStrategy[] array = strategies.toArray(new ContentNegotiationStrategy[strategies.size()]);
|
||||
return new ContentNegotiationManager(array);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,6 +55,11 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
|
||||
this.configurers.addInterceptors(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
this.configurers.configureContentNegotiation(configurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addViewControllers(ViewControllerRegistry registry) {
|
||||
this.configurers.addViewControllers(registry);
|
||||
|
||||
@@ -24,23 +24,22 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
|
||||
|
||||
/**
|
||||
* Stores and provides access to a list of interceptors. For each interceptor you can optionally
|
||||
* specify one or more URL patterns it applies to.
|
||||
* Helps with configuring a list of mapped interceptors.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Keith Donald
|
||||
*
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
public class InterceptorRegistry {
|
||||
|
||||
private final List<InterceptorRegistration> registrations = new ArrayList<InterceptorRegistration>();
|
||||
|
||||
|
||||
/**
|
||||
* Adds the provided {@link HandlerInterceptor}.
|
||||
* Adds the provided {@link HandlerInterceptor}.
|
||||
* @param interceptor the interceptor to add
|
||||
* @return An {@link InterceptorRegistration} that allows you optionally configure the
|
||||
* registered interceptor further for example adding URL patterns it should apply to.
|
||||
* @return An {@link InterceptorRegistration} that allows you optionally configure the
|
||||
* registered interceptor further for example adding URL patterns it should apply to.
|
||||
*/
|
||||
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
|
||||
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
|
||||
@@ -49,10 +48,10 @@ public class InterceptorRegistry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided {@link WebRequestInterceptor}.
|
||||
* Adds the provided {@link WebRequestInterceptor}.
|
||||
* @param interceptor the interceptor to add
|
||||
* @return An {@link InterceptorRegistration} that allows you optionally configure the
|
||||
* registered interceptor further for example adding URL patterns it should apply to.
|
||||
* @return An {@link InterceptorRegistration} that allows you optionally configure the
|
||||
* registered interceptor further for example adding URL patterns it should apply to.
|
||||
*/
|
||||
public InterceptorRegistration addWebRequestInterceptor(WebRequestInterceptor interceptor) {
|
||||
WebRequestHandlerInterceptorAdapter adapted = new WebRequestHandlerInterceptorAdapter(interceptor);
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.format.Formatter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
@@ -52,6 +55,7 @@ import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
@@ -120,8 +124,9 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
||||
*
|
||||
* <p>Both the {@link RequestMappingHandlerAdapter} and the
|
||||
* {@link ExceptionHandlerExceptionResolver} are configured with default
|
||||
* instances of the following kind, unless custom instances are provided:
|
||||
* instances of the following by default:
|
||||
* <ul>
|
||||
* <li>A {@link ContentNegotiationManager}
|
||||
* <li>A {@link DefaultFormattingConversionService}
|
||||
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
|
||||
* available on the classpath
|
||||
@@ -158,6 +163,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
|
||||
private List<Object> interceptors;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
@@ -177,6 +184,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
|
||||
handlerMapping.setOrder(0);
|
||||
handlerMapping.setInterceptors(getInterceptors());
|
||||
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
@@ -203,6 +211,43 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
protected void addInterceptors(InterceptorRegistry registry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ContentNegotiationManager} instance to use to determine
|
||||
* requested {@linkplain MediaType media types} in a given request.
|
||||
*/
|
||||
@Bean
|
||||
public ContentNegotiationManager mvcContentNegotiationManager() {
|
||||
if (this.contentNegotiationManager == null) {
|
||||
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer();
|
||||
configurer.addMediaTypes(getDefaultMediaTypes());
|
||||
configureContentNegotiation(configurer);
|
||||
this.contentNegotiationManager = configurer.getContentNegotiationManager();
|
||||
}
|
||||
return this.contentNegotiationManager;
|
||||
}
|
||||
|
||||
protected Map<String, MediaType> getDefaultMediaTypes() {
|
||||
Map<String, MediaType> map = new HashMap<String, MediaType>();
|
||||
if (romePresent) {
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to configure content negotiation.
|
||||
* @see DefaultServletHandlerConfigurer
|
||||
*/
|
||||
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a handler mapping ordered at 1 to map URL paths directly to
|
||||
* view names. To configure view controllers, override
|
||||
@@ -304,6 +349,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
addReturnValueHandlers(returnValueHandlers);
|
||||
|
||||
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
|
||||
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
adapter.setMessageConverters(getMessageConverters());
|
||||
adapter.setWebBindingInitializer(webBindingInitializer);
|
||||
adapter.setCustomArgumentResolvers(argumentResolvers);
|
||||
@@ -526,8 +572,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
}
|
||||
|
||||
/**
|
||||
* A method available to subclasses for adding default
|
||||
* {@link HandlerExceptionResolver}s.
|
||||
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
|
||||
* <p>Adds the following exception resolvers:
|
||||
* <ul>
|
||||
* <li>{@link ExceptionHandlerExceptionResolver}
|
||||
@@ -540,6 +585,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
*/
|
||||
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
|
||||
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
|
||||
exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
|
||||
exceptionHandlerExceptionResolver.afterPropertiesSet();
|
||||
|
||||
|
||||
@@ -69,6 +69,11 @@ public interface WebMvcConfigurer {
|
||||
*/
|
||||
Validator getValidator();
|
||||
|
||||
/**
|
||||
* Configure content negotiation options.
|
||||
*/
|
||||
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
|
||||
|
||||
/**
|
||||
* Add resolvers to support custom controller method argument types.
|
||||
* <p>This does not override the built-in support for resolving handler
|
||||
|
||||
@@ -57,6 +57,13 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation is empty.
|
||||
*/
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation is empty.
|
||||
|
||||
@@ -49,6 +49,12 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
|
||||
}
|
||||
}
|
||||
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
for (WebMvcConfigurer delegate : this.delegates) {
|
||||
delegate.configureContentNegotiation(configurer);
|
||||
}
|
||||
}
|
||||
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
for (WebMvcConfigurer delegate : this.delegates) {
|
||||
delegate.configureMessageConverters(converters);
|
||||
|
||||
Reference in New Issue
Block a user