In this tutorial, we will understand how Content negotiation works in Spring Web MVC.
The interface ContentNegotiationStrategy
It is a strategy interface for resolving the media types for a request.
package org.springframework.web.accept;
....
public interface ContentNegotiationStrategy {
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
throws HttpMediaTypeNotAcceptableException;
}
The method resolveMediaTypes() returns a list of media types for the current http request.
Let's see what ContentNegotiationStrategies are registered by default.
The default ContentNegotiationStrategies
In following controller, we are going to print the list of ContentNegotiationStrategies registered as beans by default
@Controller
public class MyController {
@Autowired
ApplicationContext context;
@RequestMapping("/")
@ResponseBody
public String handleRequest () {
Map<String, ContentNegotiationStrategy> map =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, ContentNegotiationStrategy.class,
true, false);
map.forEach((k, v) -> System.out.printf("%s=%s%n",
k, v.getClass().getSimpleName()));
return "response";
}
}
To try examples, run embedded tomcat (configured in pom.xml of example project below):
mvn tomcat7:run-war
Accessing http://localhost:8080/ will print following on the server console:
mvcContentNegotiationManager=ContentNegotiationManager
Understanding ContentNegotiationManager
The ContentNegotiationManager is the central class to determine requested media types for a request. Its resolveMediaTypes() method does not actually resolve the media types itself but instead delegates to a list of configured ContentNegotiationStrategies for the current request. Let's see what are those configured ContentNegotiationStrategies are:
@Controller
public class MyController {
@Autowired
ApplicationContext context;
@RequestMapping("/")
@ResponseBody
public String handleRequest () {
Map<String, ContentNegotiationStrategy> map =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, ContentNegotiationStrategy.class,
true, false);
ContentNegotiationManager m = (ContentNegotiationManager) map.get("mvcContentNegotiationManager");
List<ContentNegotiationStrategy> strategies = m.getStrategies();
strategies.forEach(s-> System.out.println(s.getClass().getName()));
return "response";
}
}
Output on the server console:
org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy
org.springframework.web.accept.HeaderContentNegotiationStrategy
HeaderContentNegotiationStrategy
It reads the 'Accept' request header to resolve the MediaTypes.
ServletPathExtensionContentNegotiationStrategy
It resolves the file extension in the request path to a media type. It also uses ServletContext.getMimeType(String) to resolve file extensions.
By default 'xml' file extension is registered targeting MediaType.APPLICATION_XML . Depending on the related dependencies present in the classpath, more extensions (json, xml, rss, atom) are registered by default. Check out the source code of WebMvcConfigurationSupport#getDefaultMediaTypes()
The Order of ContentNegotiationStrategies
The strategies are applied in an order as seen in above output. On addition of more strategies the order will be applied as specified here, or we can also look at the source code of ContentNegotiationManagerFactoryBean#afterPropertiesSet() where the strategies are added in a particular order.
Media type matching criteria
Let's see how ContentNegotiationManager applies the underlying strategies:
public class ContentNegotiationManager implements ContentNegotiationStrategy ..... {
...............
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}
...........................
}
For an HTTP request, if a strategy returns an empty result or it matches all media types i.e. MEDIA_TYPE_ALL (*/*) then next strategy is attempted till a more specific media type is matched. If none matches then an empty list is returned which is treated as MediaType.ALL by the caller.
How actually matched MediaTypes are used?
Let's find out how the default instance of ContentNegotiationManager is used during configuration time. The manager is registered as a bean by the factory method WebMvcConfigurationSupport#mvcContentNegotiationManager(). Here are the usage (caller) of this method (screenshot from IntelliJ):
As seen in above screenshot, following components of Spring MVC are set with the default instance of ContentNegotiationManager:
RequestMappingHandlerMapping For RequestMappingHandlerMapping, ContentNegotiationManager#resolveMediaTypes() are matched against the value(s) specified in RequestMapping#produces or RequestMapping#headers elements of the handler methods till a match is selected.
RequestMappingHandlerAdapter Handler's returned Object to HTTP Response body resolution RequestMappingHandlerAdapter uses the media types returned by ContentNegotiationManager#resolveMediaTypes() to select a suitable HttpMessageConverter. The writeInternal() method of the selected HttpMessageConverter is then called with handler method's returned value to perform the conversion. HTTP Request body to Handler method argument resolution Based on the request 'Content-Type' header a suitable HttpMessageConverter is selected and then the readInternal() method of the selected HttpMessageConverter is called to convert request body to Java Object. RequestMappingHandlerAdapter delegates this process to an instance of HandlerMethodArgumentResolver. The selection logic is implemented in AbstractMessageConverterMethodArgumentResolver# readWithMessageConverters() .
ResourceHandlerRegistry It applies the ContentNegotiationStrategies to the static resources which include images, css files etc.
ViewResolverRegistry It actually applies ContentNegotiationStrategies to ContentNegotiatingViewResolver which uses the media type returned by the ContentNegotiationStrategies to select a suitable View for a request during view rendering time.
ExceptionHandlerExceptionResolver Similar to RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver uses ContentNegotiationStrategies to select a suitable HttpMessageConverter to prepare the response (from the method annotated with @ExceptionHandler).
In next couple of tutorials, we will explore examples on the customization of the default ContentNegotiationStrategies and an example on writing a custom ContentNegotiationStrategy.
Example ProjectDependencies and Technologies Used: - spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
- javax.servlet-api 3.0.1 Java Servlet API
- junit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
- JDK 1.8
- Maven 3.3.9
|