Spring boot -- 学习HttpMessageConverter

文章目录

  • [1. Json格式数据获取](#1. Json格式数据获取)
  • [2. 为什么返回Json格式的数据](#2. 为什么返回Json格式的数据)
    • [2.1 注解SpringBootAppliaction](#2.1 注解SpringBootAppliaction)
      • [2.1.1 SpringBootConfiguration](#2.1.1 SpringBootConfiguration)
      • [2.1.2 ComponentScan](#2.1.2 ComponentScan)
      • [2.1.3 EnableAutoConfiguration](#2.1.3 EnableAutoConfiguration)
        • [2.1.3.1 HttpMessageConvertersAutoConfiguration](#2.1.3.1 HttpMessageConvertersAutoConfiguration)
        • [2.1.3.2 WebMvcAutoConfiguration](#2.1.3.2 WebMvcAutoConfiguration)
    • [2.2 注解RestController](#2.2 注解RestController)
      • [2.2.1 Controller](#2.2.1 Controller)
      • [2.2.2 ResponseBody](#2.2.2 ResponseBody)
      • [2.3 HttpMessageConverter](#2.3 HttpMessageConverter)
      • [2.4 MappingJackson2HttpMessageConverter](#2.4 MappingJackson2HttpMessageConverter)
      • [2.5 Request header](#2.5 Request header)
    • [2.3 总结](#2.3 总结)
  • 3.如何返回xml格式的数据
  • [4. 如何返回自定义格式的数据](#4. 如何返回自定义格式的数据)
  • [5. 总结](#5. 总结)

1. Json格式数据获取

在Spring boot项目中引入spring-boot-starter-web场景启动器之后,就可以轻松方便的获得json格式的数据返回。

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.17</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sj-1123</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sj-1123</name>
    <description>sj-1123</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

实体类Person.java

java 复制代码
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Controller.java

java 复制代码
@RestController
public class HelloController {

    @GetMapping("/hello")
    public ResponseEntity<Person> hello(String name, String age) {
        var person = new Person(name, 18);
        return ResponseEntity.ok(person);
    }
}

启动类

java 复制代码
@SpringBootApplication
public class Sj1123Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Sj1123Application.class, args);
    }
}

这样启动后就可以通过访问localhost:8080/hello返回Json格式的结果

看到这里,会有下面的一些疑问:

  • 为什么返回Json格式的数据?
  • 如何返回xml格式的数据?
  • 如何返回自定义格式的数据?

2. 为什么返回Json格式的数据

对于上面的spring boot项目,最主要的两个注解是SpringBootApplication和RestController

2.1 注解SpringBootAppliaction

这个是springBoot项目当中,最最常见及基础的一个注解。它用来表明这个配置类来生命一个或者多个Bean,并且触发自动配置和Bean扫描。这其实是一个复合注解,相当于同时注解了@SpringBootConfiguration, @EnableAutoConfiguration 和 @ComponentScan。

2.1.1 SpringBootConfiguration

这就是一个配置类注解,表明这是一个Spring Boot应用,可以当作是Configuration注解的一个springBoot应用中的一个替代选择。

2.1.2 ComponentScan

这个是组件扫描注解,用来指定要扫描哪些位置下的类,这样可以让spring 容器来找到这些BeanDefinition并生成Bean。

2.1.3 EnableAutoConfiguration

这个注解是spring Boot项目能够进行自动装配的关键。启用 Spring Application Context 的自动配置,尝试猜测和配置您可能需要的 bean。自动配置类通常基于您的类路径和您定义的 Bean 来应用。

这个注解上有一个注解Import,这个也是用来配置Bean的一种方式,AutoConfigurationImportSelector.class 这个类可以看到将会选择哪些Bean需要自动装配进来。

java 复制代码
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

深入阅读源码,可以发现,spring自动加载位于"META-INF/spring/%s.imports"里定义的自动配置类。org.springframework.boot.autoconfigure.AutoConfiguration.imports 这个文件里共有144个自动配置类,涵盖了常见的Redis,MongoDB, JDBC,es, Cassandra,web等等。

这里有两个需要关注一下:

  • org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration 这个是用来自动配置HttpMessageConverter的。
  • org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration Auto-configuration for Web MVC.
2.1.3.1 HttpMessageConvertersAutoConfiguration

这个配置类里,可以加载两个Bean:

java 复制代码
@Bean
	@ConditionalOnMissingBean
	public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
		return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(StringHttpMessageConverter.class)
	protected static class StringHttpMessageConverterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {
			Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);
			StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());
			converter.setWriteAcceptCharset(false);
			return converter;
		}

	}

一个是StringHttpMesageConverter类型的Bean,这个Bean加载的要求是当前类StringHttpMessageConverter.class存在,且Bean还没有,那就加载这个StringHttpMessageConverter的Bean。

另一个是HttpMessageConverters的Bean。

另外,这个配置类还会由Import注解导入另外三个配置类:

  • JacksonHttpMessageConvertersConfiguration.class
  • GsonHttpMessageConvertersConfiguration.class
  • JsonbHttpMessageConvertersConfiguration.class

Jackson的配置类里面,会加载MappingJackson2HttpMessageConverter,这个Converter就是用来转Json的一个重要转换类。由于没有引入Gson和Jsonb相关的包,也不会加载这两个配置类里的相关内容。

2.1.3.2 WebMvcAutoConfiguration

这个配置类,用来配置web mvc相关的内容,但是是要在WebMvcConfigurationSupport.class这个类型的Bean不存在,且Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class 这三个类存在,而且是servlet的web应用下,才会用到这个配置类。

但是WebMvcConfigurationSupport这个类型的Bean是什么时候在哪里指示加载的呢?

This is the main class providing the configuration behind the MVC Java config. It is typically imported by adding @EnableWebMvc to an application @Configuration class. An alternative more advanced option is to extend directly from this class and override methods as necessary, remembering to add @Configuration to the subclass and @Bean to overridden @Bean methods. For more details see the javadoc of @EnableWebMvc.

可以看到是在配置类上启用EnableWebMVC注解。或者是直接扩展这个类,然后加载Bean。

而在WebMvcAutoConfiguration.class里面,由这样一段代码

java 复制代码
	/**
	 * Configuration equivalent to {@code @EnableWebMvc}.
	 */
	@Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties(WebProperties.class)
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

		private final Resources resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final WebProperties webProperties;

		private final ListableBeanFactory beanFactory;

		private final WebMvcRegistrations mvcRegistrations;

		private ResourceLoader resourceLoader;

		public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,
				ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ListableBeanFactory beanFactory) {
			this.resourceProperties = webProperties.getResources();
			this.mvcProperties = mvcProperties;
			this.webProperties = webProperties;
			this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
			this.beanFactory = beanFactory;
		}

这就相当于启用了EnableWebMVC注解,加载了WebMvcConfigurationSupport类型的Bean。这个类型的Bean,配置了非常多的web相关的内容:

配置messageConverter相关的内容

java 复制代码
	/**
	 * Provides access to the shared {@link HttpMessageConverter HttpMessageConverters}
	 * used by the {@link RequestMappingHandlerAdapter} and the
	 * {@link ExceptionHandlerExceptionResolver}.
	 * <p>This method cannot be overridden; use {@link #configureMessageConverters} instead.
	 * Also see {@link #addDefaultHttpMessageConverters} for adding default message converters.
	 */
	protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

	/**
	 * Override this method to add custom {@link HttpMessageConverter HttpMessageConverters}
	 * to use with the {@link RequestMappingHandlerAdapter} and the
	 * {@link ExceptionHandlerExceptionResolver}.
	 * <p>Adding converters to the list turns off the default converters that would
	 * otherwise be registered by default. Also see {@link #addDefaultHttpMessageConverters}
	 * for adding default message converters.
	 * @param converters a list to add message converters to (initially an empty list)
	 */
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
	}

	/**
	 * Override this method to extend or modify the list of converters after it has
	 * been configured. This may be useful for example to allow default converters
	 * to be registered and then insert a custom converter through this method.
	 * @param converters the list of configured converters to extend
	 * @since 4.1.3
	 */
	protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	}

	/**
	 * Adds a set of default HttpMessageConverter instances to the given list.
	 * Subclasses can call this method from {@link #configureMessageConverters}.
	 * @param messageConverters the list to add the default message converters to
	 */
	protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(new StringHttpMessageConverter());
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new ResourceRegionHttpMessageConverter());
		if (!shouldIgnoreXml) {
			try {
				messageConverters.add(new SourceHttpMessageConverter<>());
			}
			catch (Error err) {
				// Ignore when no TransformerFactory implementation is available
			}
		}
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (!shouldIgnoreXml) {
			if (jackson2XmlPresent) {
				Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
				if (this.applicationContext != null) {
					builder.applicationContext(this.applicationContext);
				}
				messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
			}
			else if (jaxb2Present) {
				messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
			}
		}

		if (kotlinSerializationJsonPresent) {
			messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
		}
		if (jackson2Present) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
		else if (gsonPresent) {
			messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
			messageConverters.add(new JsonbHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
		}
		if (jackson2CborPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
		}
	}

这一共三个方法:

  • 第一个是直接Config custom的MessageConverter,这个就意味着默认的MessageConverter将失效了,只能用自定义的。
  • 第二个是extend MessageConverter, 这个可以添加自己的MessageConverter的同时,保留原有的MessageConverter。
  • 第三个是直接使用默认的MessageConverter。默认的一共有哪些呢?这些由spring容器根据类是否出现在class path中来进行判断。
java 复制代码
static {
		ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
		kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
	}

这里可以看到默认加载的MessageConverter有:

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • ResourceHttpMessageConverter
  • ResourceRegionHttpMessageConverter
  • AllEncompassingFormHttpMessageConverter
  • MappingJackson2HttpMessageConverter
  • SourceHttpMessageConverter
    所有默认的MessageConverter已经就位了,那什么时候使用呢?这个时候就要看另一个注解RestController了。

2.2 注解RestController

这也是一个复合注解,由两个注解组成:

@Controller

@ResponseBody

2.2.1 Controller

这个是一个特别熟悉的注解,就是表示这是一个控制器controller,它和RequestMapping注解一起,处理请求。

2.2.2 ResponseBody

这个注解就是表明返回值直接绑定到response body中。当使用@ResponseBody注解时,Spring MVC会根据请求的Content-Type头和处理方法的返回类型来选择合适的HttpMessageConverter进行数据转换。HttpMessageConverter负责将Java对象转换为响应的数据格式,并将其写入HTTP响应中。

2.3 HttpMessageConverter

HttpMessageConverter负责将对象转化为对应的数据格式,那是如何做到的呢?

java 复制代码
public interface HttpMessageConverter<T> {

    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

    List<MediaType> getSupportedMediaTypes();

    default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
    }

    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;				

}

五个方法,一个是看支持哪些MediaType,剩下4个事两组,一组是读,一组是写。在读写之前要check是都可以读、写。

简单的类图关系如下:

这里是一个标准的策略模式的实现。在处理类AbstractMessageConverterMethodProcessor.class中,依次使用MessageConverter来进行判断是否可以write,可以就将结果写入到响应体中。

java 复制代码
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignoring error response content (if any). " + ex);
					}
					return;
				}
				throw ex;
			}
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			//这里选择出要输出的MediaType类型,这个是由发起请求的Request header里面Accepte 决定的。
			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}
        
        //这里遍历spring加载的所有MessageConverter,依次遍历,如果是可以write,就调用这个MessageConverter的write方法,把结果写入response body中。写成什么样的格式,由当前这个MessageConverter的write方法决定。
		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
		}
	}

2.4 MappingJackson2HttpMessageConverter

这个是spring 官方默认的Json格式MessageConverter。这是Jackson的工具,由ObjectMapper来写出writeValue。

2.5 Request header

我们在使用postman发送请求时,没有注意设置的请求头是什么,但是里面是有一些默认值的:

甚至我们使用curl指令的时候,也可以不写这些信息

bash 复制代码
curl --location 'localhost:10083/hello?name=zhangsan'

这是因为Accept的值为*/*和缺省是一样的,这个结果被默认为返回appliaction/json格式数据。

2.3 总结

至此,可以说算是明白为什么返回Json格式数据了:

我们给服务器说,接收*/*格式数据的response。

默认的HttpMessageConverter把JAVA对象转成了Json格式写入response body中。

3.如何返回xml格式的数据

这个时候,应该已经很明确了:

  • 1.你要告诉服务端,你需要xml格式的数据:设置Request header的Accept值为application/xml
    1. 配置好服务端的MessageConverter,让它将JAVA对象转成xml格式。
      Jackson已经可以支持返回xml格式数据了,只需要引入相应依赖即可。

在pom.xml文件中引入xml格式依赖

xml 复制代码
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

然后再entity类加上注解

java 复制代码
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;

@JacksonXmlRootElement
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

这个时候再发送请求:

可以看到,已经可以返回XML格式数据了。

4. 如何返回自定义格式的数据

返回自定义格式的数据,就是要和服务端约定好,是什么样的格式,然后有converter写出来即可。

从上面的类图可以看到,我们可以直接实现HttpMessageConverter接口,也可以继承类图里的抽象类也可以。

这里提供一个继承AbstractHttpMessageConverter的例子,实现返回yaml格式:

java 复制代码
package com.example.sj1123.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class YamlMessageConverter extends AbstractHttpMessageConverter<Object> {

    private ObjectMapper objectMapper = null;

    public YamlMessageConverter() {
        super(new MediaType("application", "yaml", StandardCharsets.UTF_8));

        YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(factory);
    }


    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        try (OutputStream outputStream = outputMessage.getBody()) {
            this.objectMapper.writeValue(outputStream, o);
        }
    }
}

然后再配置这个MessageConverter作为Bean

java 复制代码
@Configuration
public class AppConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new YamlMessageConverter());
            }
        };
    }
}

这个时候,再启动应用,就可以返回yaml格式的数据了

5. 总结

HttpMessageConverter可以让读取参数、返回请求转换成指定格式。在服务端可以配置多中格式的MessageConverter。

另外,指定格式的方式不仅仅只有通过Request Header这个方式,也可以通过参数 type=yaml这种格式。

相关推荐
喵叔哟10 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生16 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
郑祎亦39 分钟前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
不是二师兄的八戒39 分钟前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
爱编程的小生1 小时前
Easyexcel(2-文件读取)
java·excel
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study2 小时前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data2 小时前
二叉树oj题解析
java·数据结构
牙牙7052 小时前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins