SPR-6375 - Register sensible default HTTP Message Converters based on what is available in the classpath

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@2547 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Arjen Poutsma 2009-11-30 12:21:13 +00:00
parent 65df1c6f4c
commit a22ab375cc
3 changed files with 117 additions and 54 deletions

View File

@ -16,40 +16,56 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
import org.w3c.dom.Element;
/** /**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses the {@code annotation-driven} element to configure * {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web
* a Spring MVC web application. * application.
* <p> *
* Responsible for: * <p>Responsible for:
* <ol> * <ol>
* <li>Registering a DefaultAnnotationHandlerMapping bean for mapping HTTP Servlet Requests to @Controller methods using @RequestMapping annotations. * <li>Registering a DefaultAnnotationHandlerMapping bean for mapping HTTP Servlet Requests to @Controller methods
* using @RequestMapping annotations.
* <li>Registering a AnnotationMethodHandlerAdapter bean for invoking annotated @Controller methods. * <li>Registering a AnnotationMethodHandlerAdapter bean for invoking annotated @Controller methods.
* Will configure the HandlerAdapter's <code>webBindingInitializer</code> property for centrally configuring @Controller DataBinder instances: * Will configure the HandlerAdapter's <code>webBindingInitializer</code> property for centrally configuring
* {@code @Controller} {@code DataBinder} instances:
* <ul> * <ul>
* <li>Configures the conversionService if specified, otherwise defaults to a fresh {@link ConversionService} instance created by the default {@link FormattingConversionServiceFactoryBean}. * <li>Configures the conversionService if specified, otherwise defaults to a fresh {@link ConversionService} instance
* <li>Configures the validator if specified, otherwise defaults to a fresh {@link Validator} instance created by the default {@link LocalValidatorFactoryBean} <i>if the JSR-303 API is present in the classpath. * created by the default {@link FormattingConversionServiceFactoryBean}.
* <li>Configures the validator if specified, otherwise defaults to a fresh {@link Validator} instance created by the
* default {@link LocalValidatorFactoryBean} <em>if the JSR-303 API is present on the classpath</em>.
* <li>Configures standard {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters},
* including the {@link Jaxb2RootElementHttpMessageConverter} <em>if JAXB2 is present on the classpath</em>, and
* the {@link MappingJacksonHttpMessageConverter} <em>if Jackson is present on the classpath</em>.
* </ul> * </ul>
* </ol> * </ol>
* *
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Arjen Poutsma
* @since 3.0 * @since 3.0
*/ */
public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@ -57,6 +73,14 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
private static final boolean jsr303Present = ClassUtils.isPresent( private static final boolean jsr303Present = ClassUtils.isPresent(
"javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()); "javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
public BeanDefinition parse(Element element, ParserContext parserContext) { public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element); Object source = parserContext.extractSource(element);
@ -74,6 +98,7 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class); RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
annAdapterDef.setSource(source); annAdapterDef.setSource(source);
annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef); annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
annAdapterDef.getPropertyValues().add("messageConverters", getMessageConverters(source));
String adapterName = parserContext.getReaderContext().registerWithGeneratedName(annAdapterDef); String adapterName = parserContext.getReaderContext().registerWithGeneratedName(annAdapterDef);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source); CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
@ -113,4 +138,20 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
} }
} }
private ManagedList<RootBeanDefinition> getMessageConverters(Object source) {
ManagedList<RootBeanDefinition> messageConverters = new ManagedList<RootBeanDefinition>();
messageConverters.setSource(source);
messageConverters.add(new RootBeanDefinition(ByteArrayHttpMessageConverter.class));
messageConverters.add(new RootBeanDefinition(StringHttpMessageConverter.class));
messageConverters.add(new RootBeanDefinition(FormHttpMessageConverter.class));
messageConverters.add(new RootBeanDefinition(SourceHttpMessageConverter.class));
if (jaxb2Present) {
messageConverters.add(new RootBeanDefinition(Jaxb2RootElementHttpMessageConverter.class));
}
if (jacksonPresent) {
messageConverters.add(new RootBeanDefinition(MappingJacksonHttpMessageConverter.class));
}
return messageConverters;
}
} }

View File

@ -319,6 +319,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
this.customModelAndViewResolvers = customModelAndViewResolvers; this.customModelAndViewResolvers = customModelAndViewResolvers;
} }
/**
* Returns the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
*/
public HttpMessageConverter<?>[] getMessageConverters() {
return messageConverters;
}
/** /**
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and * Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses. * responses.
@ -605,7 +613,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) { private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer, super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
customArgumentResolvers, messageConverters); customArgumentResolvers, getMessageConverters());
} }
@Override @Override
@ -792,8 +800,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse()); HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass(); Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (messageConverters != null) { if (getMessageConverters() != null) {
for (HttpMessageConverter messageConverter : messageConverters) { for (HttpMessageConverter messageConverter : getMessageConverters()) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
for (MediaType acceptedMediaType : acceptedMediaTypes) { for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) { if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {

View File

@ -16,20 +16,15 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
@ -38,6 +33,13 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
@ -61,35 +63,47 @@ import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
/** /**
* @author Keith Donald * @author Keith Donald
* @author Arjen Poutsma
*/ */
public class MvcNamespaceTests { public class MvcNamespaceTests {
private GenericWebApplicationContext container; private GenericWebApplicationContext appContext;
@Before @Before
public void setUp() { public void setUp() {
container = new GenericWebApplicationContext(); appContext = new GenericWebApplicationContext();
container.setServletContext(new MockServletContext()); appContext.setServletContext(new MockServletContext());
LocaleContextHolder.setLocale(Locale.US); LocaleContextHolder.setLocale(Locale.US);
} }
@Test @Test
public void testDefaultConfig() throws Exception { public void testDefaultConfig() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config.xml", getClass()));
assertEquals(4, container.getBeanDefinitionCount()); assertEquals(4, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
DefaultAnnotationHandlerMapping mapping = container.getBean(DefaultAnnotationHandlerMapping.class); DefaultAnnotationHandlerMapping mapping = appContext.getBean(DefaultAnnotationHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
assertEquals(0, mapping.getOrder()); assertEquals(0, mapping.getOrder());
AnnotationMethodHandlerAdapter adapter = container.getBean(AnnotationMethodHandlerAdapter.class); AnnotationMethodHandlerAdapter adapter = appContext.getBean(AnnotationMethodHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
assertNotNull(container.getBean(FormattingConversionServiceFactoryBean.class));
assertNotNull(container.getBean(ConversionService.class)); HttpMessageConverter[] messageConverters = adapter.getMessageConverters();
assertNotNull(container.getBean(LocalValidatorFactoryBean.class)); assertNotNull(messageConverters);
assertNotNull(container.getBean(Validator.class)); assertEquals(6, messageConverters.length);
assertTrue(ByteArrayHttpMessageConverter.class.equals(messageConverters[0].getClass()));
assertTrue(StringHttpMessageConverter.class.equals(messageConverters[1].getClass()));
assertTrue(FormHttpMessageConverter.class.equals(messageConverters[2].getClass()));
assertTrue(SourceHttpMessageConverter.class.equals(messageConverters[3].getClass()));
assertTrue(Jaxb2RootElementHttpMessageConverter.class.equals(messageConverters[4].getClass()));
assertTrue(MappingJacksonHttpMessageConverter.class.equals(messageConverters[5].getClass()));
assertNotNull(appContext.getBean(FormattingConversionServiceFactoryBean.class));
assertNotNull(appContext.getBean(ConversionService.class));
assertNotNull(appContext.getBean(LocalValidatorFactoryBean.class));
assertNotNull(appContext.getBean(Validator.class));
TestController handler = new TestController(); TestController handler = new TestController();
@ -103,12 +117,12 @@ public class MvcNamespaceTests {
@Test(expected=ConversionFailedException.class) @Test(expected=ConversionFailedException.class)
public void testCustomConversionService() throws Exception { public void testCustomConversionService() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-custom-conversion-service.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-custom-conversion-service.xml", getClass()));
assertEquals(4, container.getBeanDefinitionCount()); assertEquals(4, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
AnnotationMethodHandlerAdapter adapter = container.getBean(AnnotationMethodHandlerAdapter.class); AnnotationMethodHandlerAdapter adapter = appContext.getBean(AnnotationMethodHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
TestController handler = new TestController(); TestController handler = new TestController();
@ -122,12 +136,12 @@ public class MvcNamespaceTests {
@Test @Test
public void testCustomValidator() throws Exception { public void testCustomValidator() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-custom-validator.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-custom-validator.xml", getClass()));
assertEquals(4, container.getBeanDefinitionCount()); assertEquals(4, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
AnnotationMethodHandlerAdapter adapter = container.getBean(AnnotationMethodHandlerAdapter.class); AnnotationMethodHandlerAdapter adapter = appContext.getBean(AnnotationMethodHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
TestController handler = new TestController(); TestController handler = new TestController();
@ -138,18 +152,18 @@ public class MvcNamespaceTests {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
adapter.handle(request, response, handler); adapter.handle(request, response, handler);
assertTrue(container.getBean(TestValidator.class).validatorInvoked); assertTrue(appContext.getBean(TestValidator.class).validatorInvoked);
assertFalse(handler.recordedValidationError); assertFalse(handler.recordedValidationError);
} }
@Test @Test
public void testInterceptors() throws Exception { public void testInterceptors() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-interceptors.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-interceptors.xml", getClass()));
assertEquals(7, container.getBeanDefinitionCount()); assertEquals(7, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
DefaultAnnotationHandlerMapping mapping = container.getBean(DefaultAnnotationHandlerMapping.class); DefaultAnnotationHandlerMapping mapping = appContext.getBean(DefaultAnnotationHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
mapping.setDefaultHandler(new TestController()); mapping.setDefaultHandler(new TestController());
@ -177,12 +191,12 @@ public class MvcNamespaceTests {
@Test @Test
public void testBeanDecoration() throws Exception { public void testBeanDecoration() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-bean-decoration.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-bean-decoration.xml", getClass()));
assertEquals(6, container.getBeanDefinitionCount()); assertEquals(6, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
DefaultAnnotationHandlerMapping mapping = container.getBean(DefaultAnnotationHandlerMapping.class); DefaultAnnotationHandlerMapping mapping = appContext.getBean(DefaultAnnotationHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
mapping.setDefaultHandler(new TestController()); mapping.setDefaultHandler(new TestController());
@ -199,12 +213,12 @@ public class MvcNamespaceTests {
@Test @Test
public void testViewControllers() throws Exception { public void testViewControllers() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-view-controllers.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-view-controllers.xml", getClass()));
assertEquals(8, container.getBeanDefinitionCount()); assertEquals(8, appContext.getBeanDefinitionCount());
container.refresh(); appContext.refresh();
DefaultAnnotationHandlerMapping mapping = container.getBean(DefaultAnnotationHandlerMapping.class); DefaultAnnotationHandlerMapping mapping = appContext.getBean(DefaultAnnotationHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
mapping.setDefaultHandler(new TestController()); mapping.setDefaultHandler(new TestController());
@ -214,10 +228,10 @@ public class MvcNamespaceTests {
assertEquals(3, chain.getInterceptors().length); assertEquals(3, chain.getInterceptors().length);
assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor); assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
SimpleUrlHandlerMapping mapping2 = container.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping2); assertNotNull(mapping2);
SimpleControllerHandlerAdapter adapter = container.getBean(SimpleControllerHandlerAdapter.class); SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
request.setRequestURI("/foo"); request.setRequestURI("/foo");