mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Rework HttpMessageConverters
This commit is contained in:
parent
983ef16eae
commit
85fb1cba0b
@ -96,7 +96,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
public EndpointHandlerAdapter endpointHandlerAdapter(
|
||||
final HttpMessageConverters messageConverters) {
|
||||
EndpointHandlerAdapter adapter = new EndpointHandlerAdapter();
|
||||
adapter.setMessageConverters(messageConverters.getMessageConverters());
|
||||
adapter.setMessageConverters(messageConverters.getConverters());
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
@ -17,95 +17,141 @@
|
||||
package org.springframework.boot.autoconfigure.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
|
||||
/**
|
||||
* Convenient utility for adding and merging additional {@link HttpMessageConverter} in an
|
||||
* application context. It also modifies the default converters a bit (putting XML
|
||||
* converters at the back of the list if they are present).
|
||||
* Bean used to manage the {@link HttpMessageConverter}s used in a Spring Boot
|
||||
* application. Provides a convenient way to add and merge additional
|
||||
* {@link HttpMessageConverter}s to a web application.
|
||||
* <p>
|
||||
* An instance of this bean can be registered with specific
|
||||
* {@link #HttpMessageConverters(HttpMessageConverter...) additional converters} if
|
||||
* needed, otherwise default converters will be used.
|
||||
* <p>
|
||||
* NOTE: The default converters used are the same as standard Spring MVC (see
|
||||
* {@link WebMvcConfigurationSupport#getMessageConverters} with some slight re-ordering to
|
||||
* put XML converters at the back of the list.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @see #HttpMessageConverters(HttpMessageConverter...)
|
||||
* @see #HttpMessageConverters(Collection)
|
||||
* @see #getConverters()
|
||||
* @see #setConverters(List)
|
||||
*/
|
||||
public class HttpMessageConverters {
|
||||
|
||||
private List<HttpMessageConverter<?>> defaults;
|
||||
|
||||
private List<HttpMessageConverter<?>> overrides;
|
||||
|
||||
private Object lock = new Object();
|
||||
public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>,
|
||||
InitializingBean {
|
||||
|
||||
private List<HttpMessageConverter<?>> converters;
|
||||
|
||||
public HttpMessageConverters() {
|
||||
this(Collections.<HttpMessageConverter<?>> emptyList());
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Create a new {@link HttpMessageConverters} instance with the specified additional
|
||||
* converters.
|
||||
* @param additionalConverters additional converters to be added. New converters will
|
||||
* be added to the front of the list, overrides will replace existing items without
|
||||
* changing the order. The {@link #getConverters()} methods can be used for further
|
||||
* converter manipulation.
|
||||
*/
|
||||
public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
|
||||
this(Arrays.asList(additionalConverters));
|
||||
}
|
||||
|
||||
public HttpMessageConverters(Collection<HttpMessageConverter<?>> overrides) {
|
||||
this.overrides = new ArrayList<HttpMessageConverter<?>>(overrides);
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> getMessageConverters() {
|
||||
if (this.converters == null) {
|
||||
synchronized (this.lock) {
|
||||
if (this.converters == null) {
|
||||
getDefaultMessageConverters(); // ensure they are available
|
||||
Collection<HttpMessageConverter<?>> fallbacks = new LinkedHashSet<HttpMessageConverter<?>>();
|
||||
for (HttpMessageConverter<?> fallback : this.defaults) {
|
||||
boolean overridden = false;
|
||||
for (HttpMessageConverter<?> converter : this.overrides) {
|
||||
if (fallback.getClass()
|
||||
.isAssignableFrom(converter.getClass())) {
|
||||
if (!fallbacks.contains(converter)) {
|
||||
fallbacks.add(converter);
|
||||
overridden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!overridden) {
|
||||
fallbacks.add(fallback);
|
||||
}
|
||||
}
|
||||
Collection<HttpMessageConverter<?>> converters = new LinkedHashSet<HttpMessageConverter<?>>(
|
||||
this.overrides);
|
||||
converters.addAll(fallbacks);
|
||||
this.converters = new ArrayList<HttpMessageConverter<?>>(converters);
|
||||
}
|
||||
/**
|
||||
* Create a new {@link HttpMessageConverters} instance with the specified additional
|
||||
* converters.
|
||||
* @param additionalConverters additional converters to be added. New converters will
|
||||
* be added to the front of the list, overrides will replace existing items without
|
||||
* changing the order. The {@link #getConverters()} methods can be used for further
|
||||
* converter manipulation.
|
||||
*/
|
||||
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
|
||||
this.converters = new ArrayList<HttpMessageConverter<?>>();
|
||||
List<HttpMessageConverter<?>> defaultConverters = getDefaultConverters();
|
||||
for (HttpMessageConverter<?> converter : additionalConverters) {
|
||||
int defaultConverterIndex = indexOfItemClass(defaultConverters, converter);
|
||||
if (defaultConverterIndex == -1) {
|
||||
this.converters.add(converter);
|
||||
}
|
||||
else {
|
||||
defaultConverters.set(defaultConverterIndex, converter);
|
||||
}
|
||||
}
|
||||
this.converters.addAll(defaultConverters);
|
||||
}
|
||||
|
||||
private List<HttpMessageConverter<?>> getDefaultConverters() {
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
|
||||
converters.addAll(new WebMvcConfigurationSupport() {
|
||||
public List<HttpMessageConverter<?>> defaultMessageConverters() {
|
||||
return super.getMessageConverters();
|
||||
}
|
||||
}.defaultMessageConverters());
|
||||
reorderXmlConvertersToEnd(converters);
|
||||
return converters;
|
||||
}
|
||||
|
||||
private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) {
|
||||
List<HttpMessageConverter<?>> xml = new ArrayList<HttpMessageConverter<?>>();
|
||||
for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator
|
||||
.hasNext();) {
|
||||
HttpMessageConverter<?> converter = iterator.next();
|
||||
if (converter instanceof AbstractXmlHttpMessageConverter) {
|
||||
xml.add(converter);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
converters.addAll(xml);
|
||||
}
|
||||
|
||||
private <E> int indexOfItemClass(List<E> list, E item) {
|
||||
Class<? extends Object> itemClass = item.getClass();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (list.get(i).getClass().isAssignableFrom(itemClass)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<HttpMessageConverter<?>> iterator() {
|
||||
return getConverters().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mutable list of the converters in the order that they will be registered.
|
||||
* Values in the list cannot be modified once the bean has been initialized.
|
||||
* @return the converters
|
||||
*/
|
||||
public List<HttpMessageConverter<?>> getConverters() {
|
||||
return this.converters;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> getDefaultMessageConverters() {
|
||||
if (this.defaults == null) {
|
||||
synchronized (this.lock) {
|
||||
if (this.defaults == null) {
|
||||
this.defaults = new ArrayList<HttpMessageConverter<?>>();
|
||||
this.defaults.addAll(new WebMvcConfigurationSupport() {
|
||||
public List<HttpMessageConverter<?>> defaultMessageConverters() {
|
||||
return super.getMessageConverters();
|
||||
}
|
||||
}.defaultMessageConverters());
|
||||
List<HttpMessageConverter<?>> xmls = new ArrayList<HttpMessageConverter<?>>();
|
||||
for (HttpMessageConverter<?> converter : this.defaults) {
|
||||
// Shift XML converters to the back of the list so they only get
|
||||
// used if nothing else works...
|
||||
if (converter instanceof AbstractXmlHttpMessageConverter) {
|
||||
xmls.add(converter);
|
||||
}
|
||||
}
|
||||
this.defaults.removeAll(xmls);
|
||||
this.defaults.addAll(xmls);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(this.defaults);
|
||||
/**
|
||||
* Set the converters to use, replacing any existing values. This method can only be
|
||||
* called before the bean has been initialized.
|
||||
* @param converters the converters to set
|
||||
*/
|
||||
public void setConverters(List<HttpMessageConverter<?>> converters) {
|
||||
Assert.state(!this.initialized, "Unable to set converters once initialized");
|
||||
this.converters = converters;
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.initialized = true;
|
||||
this.converters = Collections.unmodifiableList(this.converters);
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ public class WebMvcAutoConfiguration {
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.addAll(this.messageConverters.getMessageConverters());
|
||||
converters.addAll(this.messageConverters.getConverters());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -42,6 +42,8 @@ import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpMessageConvertersAutoConfiguration}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class HttpMessageConvertersAutoConfigurationTests {
|
||||
@ -67,7 +69,7 @@ public class HttpMessageConvertersAutoConfigurationTests {
|
||||
converter.getObjectMapper());
|
||||
HttpMessageConverters converters = this.context
|
||||
.getBean(HttpMessageConverters.class);
|
||||
assertTrue(converters.getMessageConverters().contains(converter));
|
||||
assertTrue(converters.getConverters().contains(converter));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -16,43 +16,96 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class HttpMessageConvertersTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void defaultsCreated() {
|
||||
HttpMessageConverters messageConverters = new HttpMessageConverters();
|
||||
assertFalse(messageConverters.getDefaultMessageConverters().isEmpty());
|
||||
public void containsDefaults() throws Exception {
|
||||
HttpMessageConverters converters = new HttpMessageConverters();
|
||||
List<Class<?>> converterClasses = new ArrayList<Class<?>>();
|
||||
for (HttpMessageConverter<?> converter : converters) {
|
||||
converterClasses.add(converter.getClass());
|
||||
}
|
||||
assertThat(converterClasses, equalTo(Arrays.<Class<?>> asList(
|
||||
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
|
||||
ResourceHttpMessageConverter.class, SourceHttpMessageConverter.class,
|
||||
AllEncompassingFormHttpMessageConverter.class,
|
||||
MappingJackson2HttpMessageConverter.class,
|
||||
Jaxb2RootElementHttpMessageConverter.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canModifyBeforeInitialize() throws Exception {
|
||||
HttpMessageConverters converters = new HttpMessageConverters();
|
||||
HttpMessageConverter<?> converter = mock(HttpMessageConverter.class);
|
||||
converters.getConverters().add(converter);
|
||||
assertThat(converters.getConverters().contains(converter), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotModifyAfterInitialize() throws Exception {
|
||||
HttpMessageConverters converters = new HttpMessageConverters();
|
||||
converters.afterPropertiesSet();
|
||||
this.thrown.expect(UnsupportedOperationException.class);
|
||||
converters.getConverters().add(mock(HttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSetBeforeInitialize() throws Exception {
|
||||
HttpMessageConverters converters = new HttpMessageConverters();
|
||||
converters.setConverters(new ArrayList<HttpMessageConverter<?>>());
|
||||
assertThat(converters.getConverters().size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotSetAfterInitailzie() throws Exception {
|
||||
HttpMessageConverters converters = new HttpMessageConverters();
|
||||
converters.afterPropertiesSet();
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
converters.setConverters(new ArrayList<HttpMessageConverter<?>>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideExistingConverter() {
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
HttpMessageConverters messageConverters = new HttpMessageConverters(
|
||||
Arrays.<HttpMessageConverter<?>> asList(converter));
|
||||
assertTrue(messageConverters.getMessageConverters().contains(converter));
|
||||
HttpMessageConverters converters = new HttpMessageConverters(converter);
|
||||
assertTrue(converters.getConverters().contains(converter));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addNewOne() {
|
||||
HttpMessageConverter<?> converter = Mockito.mock(HttpMessageConverter.class);
|
||||
HttpMessageConverters messageConverters = new HttpMessageConverters(
|
||||
Arrays.<HttpMessageConverter<?>> asList(converter));
|
||||
assertTrue(messageConverters.getMessageConverters().contains(converter));
|
||||
assertEquals(converter, messageConverters.getMessageConverters().get(0));
|
||||
public void addNewConverter() {
|
||||
HttpMessageConverter<?> converter = mock(HttpMessageConverter.class);
|
||||
HttpMessageConverters converters = new HttpMessageConverters(converter);
|
||||
assertTrue(converters.getConverters().contains(converter));
|
||||
assertEquals(converter, converters.getConverters().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -83,8 +83,7 @@ public class WebMvcAutoConfigurationTests {
|
||||
assertEquals(3, this.context.getBeanNamesForType(HandlerAdapter.class).length);
|
||||
assertFalse(this.context.getBean(RequestMappingHandlerAdapter.class)
|
||||
.getMessageConverters().isEmpty());
|
||||
assertEquals(this.context.getBean(HttpMessageConverters.class)
|
||||
.getMessageConverters(),
|
||||
assertEquals(this.context.getBean(HttpMessageConverters.class).getConverters(),
|
||||
this.context.getBean(RequestMappingHandlerAdapter.class)
|
||||
.getMessageConverters());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user