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
|
||||
* one or more {@link ContentNegotiationStrategy} instances via simple setters.
|
||||
* The following table shows setters, resulting strategy instances, and if in
|
||||
* use by default:
|
||||
* one or more {@link ContentNegotiationStrategy} instances.
|
||||
*
|
||||
* <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>
|
||||
* <tr>
|
||||
|
@ -73,17 +78,12 @@ import org.springframework.web.context.ServletContextAware;
|
|||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>The order in which strategies are configured is fixed. Setters may only
|
||||
* turn individual strategies on or off. If you need a custom order for any
|
||||
* reason simply instantiate {@code ContentNegotiationManager} directly.
|
||||
*
|
||||
* <p>For the path extension and parameter strategies you may explicitly add
|
||||
* {@link #setMediaTypes MediaType mappings}. This will be used to resolve path
|
||||
* 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.
|
||||
* <strong>Note:</strong> if you must use URL-based content type resolution,
|
||||
* the use of a query parameter is simpler and preferable to the use of a path
|
||||
* extension since the latter can cause issues with URI variables, path
|
||||
* parameters, and URI decoding. Consider setting {@link #setFavorPathExtension}
|
||||
* to {@literal false} or otherwise set the strategies to use explicitly via
|
||||
* {@link #setStrategies(List)}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
|
@ -91,6 +91,10 @@ import org.springframework.web.context.ServletContextAware;
|
|||
public class ContentNegotiationManagerFactoryBean
|
||||
implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
|
||||
|
||||
@Nullable
|
||||
private List<ContentNegotiationStrategy> strategies;
|
||||
|
||||
|
||||
private boolean favorPathExtension = true;
|
||||
|
||||
private boolean favorParameter = false;
|
||||
|
@ -116,6 +120,18 @@ public class ContentNegotiationManagerFactoryBean
|
|||
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
|
||||
* the requested media type.
|
||||
|
@ -280,41 +296,46 @@ public class ContentNegotiationManagerFactoryBean
|
|||
public ContentNegotiationManager build() {
|
||||
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
|
||||
|
||||
if (this.favorPathExtension) {
|
||||
PathExtensionContentNegotiationStrategy strategy;
|
||||
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);
|
||||
if (this.strategies != null) {
|
||||
strategies.addAll(this.strategies);
|
||||
}
|
||||
|
||||
if (this.favorParameter) {
|
||||
ParameterContentNegotiationStrategy strategy =
|
||||
new ParameterContentNegotiationStrategy(this.mediaTypes);
|
||||
strategy.setParameterName(this.parameterName);
|
||||
if (this.useRegisteredExtensionsOnly != null) {
|
||||
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
|
||||
else {
|
||||
if (this.favorPathExtension) {
|
||||
PathExtensionContentNegotiationStrategy strategy;
|
||||
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 {
|
||||
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) {
|
||||
strategies.add(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
if (!this.ignoreAcceptHeader) {
|
||||
strategies.add(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
|
||||
if (this.defaultNegotiationStrategy != null) {
|
||||
strategies.add(this.defaultNegotiationStrategy);
|
||||
if (this.defaultNegotiationStrategy != null) {
|
||||
strategies.add(this.defaultNegotiationStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
|
||||
|
|
|
@ -89,6 +89,25 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
|||
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
|
||||
public void favorPath() throws Exception {
|
||||
this.factoryBean.setFavorPathExtension(true);
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.web.servlet.config.annotation;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
|
@ -34,9 +35,14 @@ import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
|||
|
||||
/**
|
||||
* Creates a {@code ContentNegotiationManager} and configures it with
|
||||
* one or more {@link ContentNegotiationStrategy} instances. The following shows
|
||||
* the resulting strategy instances, the methods used to configured them, and
|
||||
* whether enabled by default:
|
||||
* one or more {@link ContentNegotiationStrategy} instances.
|
||||
*
|
||||
* <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>
|
||||
* <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
|
||||
* them on or off.
|
||||
*
|
||||
* <p>For the path extension and parameter strategies you may explicitly add
|
||||
* {@link #mediaType MediaType mappings}. Those will be used to resolve path
|
||||
* extensions and/or a query parameter value such as "json" to a concrete media
|
||||
* type such as "application/json".
|
||||
*
|
||||
* <p>The path extension strategy will also use {@link ServletContext#getMimeType}
|
||||
* and the {@link MediaTypeFactory} to resolve a path
|
||||
* extension to a MediaType.
|
||||
* <strong>Note:</strong> if you must use URL-based content type resolution,
|
||||
* the use of a query parameter is simpler and preferable to the use of a path
|
||||
* extension since the latter can cause issues with URI variables, path
|
||||
* parameters, and URI decoding. Consider setting {@link #favorPathExtension}
|
||||
* to {@literal false} or otherwise set the strategies to use explicitly via
|
||||
* {@link #strategies(List)}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @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
|
||||
* the requested media type.
|
||||
|
|
|
@ -5198,10 +5198,14 @@ And in XML use the `<mvc:interceptors>` element:
|
|||
[[mvc-config-content-negotiation]]
|
||||
=== Content Negotiation
|
||||
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
|
||||
"Accept" header, a specific query parameter, or to fall back on a default content
|
||||
type when nothing is requested. By default the path extension in the request URI
|
||||
is checked first and the "Accept" header is checked second.
|
||||
The available options are to check a query parameter, the URL path for a file extension,
|
||||
the "Accept" header, use a fixed list, or a custom strategy.
|
||||
|
||||
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
|
||||
default if corresponding dependencies are on the classpath. Additional
|
||||
|
|
Loading…
Reference in New Issue