Set all strategies in CNM factory bean
ContentNegotiationManagerFactoryBean now provides an option to explicitly set the strategies to use vs customizing a fixed list of default strategies. Issue: SPR-11114
This commit is contained in:
parent
befacf4a35
commit
134ceac58e
|
@ -36,9 +36,14 @@ import org.springframework.web.context.ServletContextAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory to create a {@code ContentNegotiationManager} and configure it with
|
* Factory to create a {@code ContentNegotiationManager} and configure it with
|
||||||
* one or more {@link ContentNegotiationStrategy} instances via simple setters.
|
* one or more {@link ContentNegotiationStrategy} instances.
|
||||||
* The following table shows setters, resulting strategy instances, and if in
|
*
|
||||||
* use by default:
|
* <p>As of 5.0 you can set the exact strategies to use via
|
||||||
|
* {@link #setStrategies(List)}.
|
||||||
|
*
|
||||||
|
* <p>As an alternative you can also rely on the set of defaults described below
|
||||||
|
* which can be turned on or off or customized through the methods of this
|
||||||
|
* builder:
|
||||||
*
|
*
|
||||||
* <table>
|
* <table>
|
||||||
* <tr>
|
* <tr>
|
||||||
|
@ -73,17 +78,12 @@ import org.springframework.web.context.ServletContextAware;
|
||||||
* </tr>
|
* </tr>
|
||||||
* </table>
|
* </table>
|
||||||
*
|
*
|
||||||
* <p>The order in which strategies are configured is fixed. Setters may only
|
* <strong>Note:</strong> if you must use URL-based content type resolution,
|
||||||
* turn individual strategies on or off. If you need a custom order for any
|
* the use of a query parameter is simpler and preferable to the use of a path
|
||||||
* reason simply instantiate {@code ContentNegotiationManager} directly.
|
* extension since the latter can cause issues with URI variables, path
|
||||||
*
|
* parameters, and URI decoding. Consider setting {@link #setFavorPathExtension}
|
||||||
* <p>For the path extension and parameter strategies you may explicitly add
|
* to {@literal false} or otherwise set the strategies to use explicitly via
|
||||||
* {@link #setMediaTypes MediaType mappings}. This will be used to resolve path
|
* {@link #setStrategies(List)}.
|
||||||
* extensions or a parameter value such as "json" to a media type such as
|
|
||||||
* "application/json".
|
|
||||||
*
|
|
||||||
* <p>The path extension strategy will also use {@link ServletContext#getMimeType}
|
|
||||||
* and {@link MediaTypeFactory} to resolve a path extension to a MediaType.
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
|
@ -91,6 +91,10 @@ import org.springframework.web.context.ServletContextAware;
|
||||||
public class ContentNegotiationManagerFactoryBean
|
public class ContentNegotiationManagerFactoryBean
|
||||||
implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
|
implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private List<ContentNegotiationStrategy> strategies;
|
||||||
|
|
||||||
|
|
||||||
private boolean favorPathExtension = true;
|
private boolean favorPathExtension = true;
|
||||||
|
|
||||||
private boolean favorParameter = false;
|
private boolean favorParameter = false;
|
||||||
|
@ -116,6 +120,18 @@ public class ContentNegotiationManagerFactoryBean
|
||||||
private ServletContext servletContext;
|
private ServletContext servletContext;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the exact list of strategies to use.
|
||||||
|
* <p><strong>Note:</strong> use of this method is mutually exclusive with
|
||||||
|
* use of all other setters in this class which customize a default, fixed
|
||||||
|
* set of strategies. See class level doc for more details.
|
||||||
|
* @param strategies the strategies to use
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public void setStrategies(@Nullable List<ContentNegotiationStrategy> strategies) {
|
||||||
|
this.strategies = (strategies != null ? new ArrayList<>(strategies) : null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the path extension in the URL path should be used to determine
|
* Whether the path extension in the URL path should be used to determine
|
||||||
* the requested media type.
|
* the requested media type.
|
||||||
|
@ -280,41 +296,46 @@ public class ContentNegotiationManagerFactoryBean
|
||||||
public ContentNegotiationManager build() {
|
public ContentNegotiationManager build() {
|
||||||
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
|
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
|
||||||
|
|
||||||
if (this.favorPathExtension) {
|
if (this.strategies != null) {
|
||||||
PathExtensionContentNegotiationStrategy strategy;
|
strategies.addAll(this.strategies);
|
||||||
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
|
|
||||||
strategy = new ServletPathExtensionContentNegotiationStrategy(
|
|
||||||
this.servletContext, this.mediaTypes);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
|
|
||||||
}
|
|
||||||
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
|
|
||||||
if (this.useRegisteredExtensionsOnly != null) {
|
|
||||||
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
|
|
||||||
}
|
|
||||||
strategies.add(strategy);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (this.favorParameter) {
|
if (this.favorPathExtension) {
|
||||||
ParameterContentNegotiationStrategy strategy =
|
PathExtensionContentNegotiationStrategy strategy;
|
||||||
new ParameterContentNegotiationStrategy(this.mediaTypes);
|
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
|
||||||
strategy.setParameterName(this.parameterName);
|
strategy = new ServletPathExtensionContentNegotiationStrategy(
|
||||||
if (this.useRegisteredExtensionsOnly != null) {
|
this.servletContext, this.mediaTypes);
|
||||||
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
|
}
|
||||||
|
else {
|
||||||
|
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
|
||||||
|
}
|
||||||
|
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
|
||||||
|
if (this.useRegisteredExtensionsOnly != null) {
|
||||||
|
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
|
||||||
|
}
|
||||||
|
strategies.add(strategy);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
|
if (this.favorParameter) {
|
||||||
|
ParameterContentNegotiationStrategy strategy =
|
||||||
|
new ParameterContentNegotiationStrategy(this.mediaTypes);
|
||||||
|
strategy.setParameterName(this.parameterName);
|
||||||
|
if (this.useRegisteredExtensionsOnly != null) {
|
||||||
|
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
|
||||||
|
}
|
||||||
|
strategies.add(strategy);
|
||||||
}
|
}
|
||||||
strategies.add(strategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.ignoreAcceptHeader) {
|
if (!this.ignoreAcceptHeader) {
|
||||||
strategies.add(new HeaderContentNegotiationStrategy());
|
strategies.add(new HeaderContentNegotiationStrategy());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.defaultNegotiationStrategy != null) {
|
if (this.defaultNegotiationStrategy != null) {
|
||||||
strategies.add(this.defaultNegotiationStrategy);
|
strategies.add(this.defaultNegotiationStrategy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
|
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
|
||||||
|
|
|
@ -89,6 +89,25 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
Collections.singletonList(MediaType.IMAGE_GIF), manager.resolveMediaTypes(this.webRequest));
|
Collections.singletonList(MediaType.IMAGE_GIF), manager.resolveMediaTypes(this.webRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void explicitStrategies() throws Exception {
|
||||||
|
Map<String, MediaType> mediaTypes = Collections.singletonMap("bar", new MediaType("application", "bar"));
|
||||||
|
ParameterContentNegotiationStrategy strategy1 = new ParameterContentNegotiationStrategy(mediaTypes);
|
||||||
|
HeaderContentNegotiationStrategy strategy2 = new HeaderContentNegotiationStrategy();
|
||||||
|
List<ContentNegotiationStrategy> strategies = Arrays.asList(strategy1, strategy2);
|
||||||
|
this.factoryBean.setStrategies(strategies);
|
||||||
|
this.factoryBean.afterPropertiesSet();
|
||||||
|
ContentNegotiationManager manager = this.factoryBean.getObject();
|
||||||
|
|
||||||
|
assertEquals(strategies, manager.getStrategies());
|
||||||
|
|
||||||
|
this.servletRequest.setRequestURI("/flower");
|
||||||
|
this.servletRequest.addParameter("format", "bar");
|
||||||
|
assertEquals(Collections.singletonList(new MediaType("application", "bar")),
|
||||||
|
manager.resolveMediaTypes(this.webRequest));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void favorPath() throws Exception {
|
public void favorPath() throws Exception {
|
||||||
this.factoryBean.setFavorPathExtension(true);
|
this.factoryBean.setFavorPathExtension(true);
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.web.servlet.config.annotation;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
|
@ -34,9 +35,14 @@ import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ContentNegotiationManager} and configures it with
|
* Creates a {@code ContentNegotiationManager} and configures it with
|
||||||
* one or more {@link ContentNegotiationStrategy} instances. The following shows
|
* one or more {@link ContentNegotiationStrategy} instances.
|
||||||
* the resulting strategy instances, the methods used to configured them, and
|
*
|
||||||
* whether enabled by default:
|
* <p>As of 5.0 you can set the exact strategies to use via
|
||||||
|
* {@link #strategies(List)}.
|
||||||
|
*
|
||||||
|
* <p>As an alternative you can also rely on the set of defaults described below
|
||||||
|
* which can be turned on or off or customized through the methods of this
|
||||||
|
* builder:
|
||||||
*
|
*
|
||||||
* <table>
|
* <table>
|
||||||
* <tr>
|
* <tr>
|
||||||
|
@ -74,14 +80,12 @@ import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||||
* <p>The order in which strategies are configured is fixed. You can only turn
|
* <p>The order in which strategies are configured is fixed. You can only turn
|
||||||
* them on or off.
|
* them on or off.
|
||||||
*
|
*
|
||||||
* <p>For the path extension and parameter strategies you may explicitly add
|
* <strong>Note:</strong> if you must use URL-based content type resolution,
|
||||||
* {@link #mediaType MediaType mappings}. Those will be used to resolve path
|
* the use of a query parameter is simpler and preferable to the use of a path
|
||||||
* extensions and/or a query parameter value such as "json" to a concrete media
|
* extension since the latter can cause issues with URI variables, path
|
||||||
* type such as "application/json".
|
* parameters, and URI decoding. Consider setting {@link #favorPathExtension}
|
||||||
*
|
* to {@literal false} or otherwise set the strategies to use explicitly via
|
||||||
* <p>The path extension strategy will also use {@link ServletContext#getMimeType}
|
* {@link #strategies(List)}.
|
||||||
* and the {@link MediaTypeFactory} to resolve a path
|
|
||||||
* extension to a MediaType.
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
|
@ -103,6 +107,18 @@ public class ContentNegotiationConfigurer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the exact list of strategies to use.
|
||||||
|
* <p><strong>Note:</strong> use of this method is mutually exclusive with
|
||||||
|
* use of all other setters in this class which customize a default, fixed
|
||||||
|
* set of strategies. See class level doc for more details.
|
||||||
|
* @param strategies the strategies to use
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public void strategies(@Nullable List<ContentNegotiationStrategy> strategies) {
|
||||||
|
this.factory.setStrategies(strategies);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the path extension in the URL path should be used to determine
|
* Whether the path extension in the URL path should be used to determine
|
||||||
* the requested media type.
|
* the requested media type.
|
||||||
|
|
|
@ -5198,10 +5198,14 @@ And in XML use the `<mvc:interceptors>` element:
|
||||||
[[mvc-config-content-negotiation]]
|
[[mvc-config-content-negotiation]]
|
||||||
=== Content Negotiation
|
=== Content Negotiation
|
||||||
You can configure how Spring MVC determines the requested media types from the request.
|
You can configure how Spring MVC determines the requested media types from the request.
|
||||||
The available options are to check the URL path for a file extension, check the
|
The available options are to check a query parameter, the URL path for a file extension,
|
||||||
"Accept" header, a specific query parameter, or to fall back on a default content
|
the "Accept" header, use a fixed list, or a custom strategy.
|
||||||
type when nothing is requested. By default the path extension in the request URI
|
|
||||||
is checked first and the "Accept" header is checked second.
|
By default for backwards compatibility the path extension in the request URI is checked
|
||||||
|
first and the "Accept" header is checked second. However if you must use URL-based content
|
||||||
|
type resolution, we highly recommend using the query parameter strategy over the path
|
||||||
|
extension since the latter can cause issues with URI variables, path parameters, and also
|
||||||
|
in combination with URI decoding.
|
||||||
|
|
||||||
The MVC Java config and the MVC namespace register `json`, `xml`, `rss`, `atom` by
|
The MVC Java config and the MVC namespace register `json`, `xml`, `rss`, `atom` by
|
||||||
default if corresponding dependencies are on the classpath. Additional
|
default if corresponding dependencies are on the classpath. Additional
|
||||||
|
|
Loading…
Reference in New Issue