一、前言
web相关知识探索六中研究了接口数据响应与简单的内容协商的底层原理。本次主要是探索一下内容协商的底层原理。
二、内容协商
一、什么是内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
二、内容协商过程演示
一、问题
在项目开发中,接口大都返回的是json格式的数据。而前端调用后端接口一般接受数据类型也是写为json格式。例如天猫的商品详情接口。
但是,同一个接口别的调用方可能需要的是一个xml格式或者其他格式的数据,如果相同功能又再写一个接口去处理,只是为了返回不同格式的数据,这或许是一种处理方式,但是总觉得不够优雅,这个时候就可以使用springhmvc的内容协商机制去处理了。他会根据请求方能够接受的数据类型,来返回不同类型的数据,非常好的处理了上面的问题。
二、解决问题
一、首先需要引入支持xml的依赖
支持json转化的依赖,在引入spring-boot-starter-web这个依赖时会自动引入spring-boot-starter-json这个依赖,里面会有对应的jackson转换依赖包。现在需要引入的是转换xml的依赖包
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
@GetMapping("/test/person")public Person testEntity(){return new Person();}
同样的接口,使用postman发送,指定接受类型为json,返回值就会返回json类型的数据格式。
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
利用sprigmvc的内容协商原理,即可完美解决之前数据格式的问题
三、内容协商原理
从请求进来到接口处理这部分省略,直接到获取到了接口返回值这一步。获取到接口返回值之后,由于接口上使用的是@RestController注解,带了@ResponseBody注解,所以会进入下图这个类里面的方法。
这行代码底层用的就是messageConverters通过匹配客户端接受类型和服务器能够处理的类型,然后返回最终合适的数据格式类型。this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class valueType;Object targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;} else {body = value;valueType = this.getReturnValueType(value, returnType);targetType = GenericTypeResolver.resolveType(this.getGenericType(returnType), returnType.getContainingClass());}if (this.isResourceType(value, returnType)) {outputMessage.getHeaders().set("Accept-Ranges", "bytes");if (value != null && inputMessage.getHeaders().getFirst("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 var19) {outputMessage.getHeaders().set("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 (this.logger.isDebugEnabled()) {this.logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;} else {// 没有就进入下面流程HttpServletRequest request = inputMessage.getServletRequest();List acceptableTypes;try {// 获取客户端支持接受的内容类型,主要就是获取请求头中的accept字段数据acceptableTypes = this.getAcceptableMediaTypes(request);} catch (HttpMediaTypeNotAcceptableException var20) {int series = outputMessage.getServletResponse().getStatus() / 100;if (body != null && series != 4 && series != 5) {throw var20;}if (this.logger.isDebugEnabled()) {this.logger.debug("Ignoring error response content (if any). " + var20);}return;}// 获取服务端能够响应的数据类型List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}// 这里就开始外层循环 客户端支持接受的内容类型,内层循环服务端能够响应的数据类型,匹配客户端能够接收,服务器能够处理的,最合适的处理类型List<MediaType> mediaTypesToUse = new ArrayList();Iterator var15 = acceptableTypes.iterator();MediaType mediaType;while(var15.hasNext()) {mediaType = (MediaType)var15.next();Iterator var17 = producibleTypes.iterator();while(var17.hasNext()) {MediaType producibleType = (MediaType)var17.next();if (mediaType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));}}}// 没有找到最合适的,那么就会报错if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (this.logger.isDebugEnabled()) {this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}// 排个序 MediaType.sortBySpecificityAndQuality(mediaTypesToUse);// var15 = mediaTypesToUse.iterator();while(var15.hasNext()) {mediaType = (MediaType)var15.next();// 拿到一个匹配类型就返回if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (this.logger.isDebugEnabled()) {this.logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);}}HttpMessageConverter converter;GenericHttpMessageConverter genericConverter;label183: {if (selectedMediaType != null) {// 获取到当前需要返回的数据类型,也就是前面找到的客户端能够接受的和服务器能够处理的最合适的类型selectedMediaType = selectedMediaType.removeQualityValue();// 循环遍历所有的消息转换器Iterator var23 = this.messageConverters.iterator();while(var23.hasNext()) {converter = (HttpMessageConverter)var23.next();genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;if (genericConverter != null) {// 找到能够将接口返回的数据,转为上面找到的selectedMediaType两方都能够接受的处理类型,这里就是把person对象转为json或xmlif (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {break label183;}} else if (converter.canWrite(valueType, selectedMediaType)) {break label183;}}}if (body != null) {Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMediaTypeNotAcceptableException(this.getSupportedMediaTypes(body.getClass()));}throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}return;}// 接口返回的响应数据 body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);if (body != null) {LogFormatUtils.traceDebug(this.logger, (traceOn) -> {return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";});this.addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {// 到这里就开始对响应数据,转化为合适的数据类型了,比如xml。进去就会选择对应的类把数据处理成xml了genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);} else {converter.write(body, selectedMediaType, outputMessage);}} else if (this.logger.isDebugEnabled()) {this.logger.debug("Nothing to write: null body");}}// 一、下面这部分是 获取客户端支持接受的内容类型// AbstractMessageConverterMethodProcessor类中的方法,回去请求头能够接受的类型数据private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {// contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略,return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));}// ContentNegotiationManager类写的方法,public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {// 这里就是循环遍历能够处理请求头数据的类Iterator var2 = this.strategies.iterator();List mediaTypes;do {if (!var2.hasNext()) {return MEDIA_TYPE_ALL_LIST;}// 这里调用了具体的获取方式ContentNegotiationStrategy strategy = (ContentNegotiationStrategy)var2.next();mediaTypes = strategy.resolveMediaTypes(request);} while(mediaTypes.equals(MEDIA_TYPE_ALL_LIST));return mediaTypes;}// HeaderContentNegotiationStrategy类下的方法,这里就是具体的获取方式public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {// 就是从请求头中获取Accept字段数据String[] headerValueArray = request.getHeaderValues("Accept");if (headerValueArray == null) {// 如果空的就返回能够接受所有的数据类型return MEDIA_TYPE_ALL_LIST;} else {// 不空就把媒体类型从String转为MediaType,并且放入到集合当中List<String> headerValues = Arrays.asList(headerValueArray);try {List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);MediaType.sortBySpecificityAndQuality(mediaTypes);return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;} catch (InvalidMediaTypeException var5) {throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + var5.getMessage());}}}// 二、下面这部分是获取服务端的能够返回的数据类型// AbstractMessageConverterMethodProcessor类下的方法protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {// 获取一个默认的媒体类型,一般是空Set<MediaType> mediaTypes = (Set)request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!CollectionUtils.isEmpty(mediaTypes)) {return new ArrayList(mediaTypes);} else {List<MediaType> result = new ArrayList();// 循环遍历所有的消息转换器Iterator var6 = this.messageConverters.iterator();while(true) {while(var6.hasNext()) {HttpMessageConverter<?> converter = (HttpMessageConverter)var6.next();if (converter instanceof GenericHttpMessageConverter && targetType != null) {.// 如果支持接口返回值类型的操作就加入到集合当中,有些转换器只支持String,有些只支持资源类型,有些什么都支持。例如,当前测试接口返回的是Person对象,StringHttpMessageConverter// 这个转换器就不支持,这个只支持String类型的接口返回数据if (((GenericHttpMessageConverter)converter).canWrite(targetType, valueClass, (MediaType)null)) {// 只要是能够支持转化的都放到集合中保存起来result.addAll(converter.getSupportedMediaTypes(valueClass));}} else if (converter.canWrite(valueClass, (MediaType)null)) {result.addAll(converter.getSupportedMediaTypes(valueClass));}}return (List)(result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result);}}}// 第三部分 AbstractGenericHttpMessageConverter里面的方法,将接口数据转为客户端要求的xml类型的数据public final void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {HttpHeaders headers = outputMessage.getHeaders();// 往响应头设置响应类型,例如xml类型this.addDefaultHeaders(headers, t, contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage)outputMessage;streamingOutputMessage.setBody((outputStream) -> {this.writeInternal(t, type, new HttpOutputMessage() {public OutputStream getBody() {return outputStream;}public HttpHeaders getHeaders() {return headers;}});});} else {// 然后进入这里进行数据类型转换,这里就会进到AbstractJackson2HttpMessageConverter类里面了,具体如何转换就是jackson里面的原理了this.writeInternal(t, type, outputMessage);outputMessage.getBody().flush();}}
三、自定义消息转换规则
了解清除了MessageConverter原理后,可以通过自定义MessageConverter完成内容协商定制化。
首先是在spring-boot-autoconfigure包下有一个类WebMvcAutoConfiguration,里面有一个静态内部类,实现了WebMvcConfigurer接口,在静态内部类里面有个方法将所有的消息转换器添加进了容器当中。
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {this.messageConvertersProvider.ifAvailable((customConverters) -> {converters.addAll(customConverters.getConverters());}); }
上面方法中customConverters.getConverters(),代码,是调用的
HttpMessageConverters类面的方法。public List<HttpMessageConverter<?>> getConverters() {return this.converters; }返回的是this.converters成员变量。这个成员变量是在对象创建时进行了赋值
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {// 主要是在这行代码中this.getDefaultConverters()就是添加默认的消息转换器List<HttpMessageConverter<?>> combined = this.getCombinedConverters(converters, addDefaultConverters ? this.getDefaultConverters() : Collections.emptyList());combined = this.postProcessConverters(combined);this.converters = Collections.unmodifiableList(combined);
}// 从this.getDefaultConverters()这里进入private List<HttpMessageConverter<?>> getDefaultConverters() {List<HttpMessageConverter<?>> converters = new ArrayList();// 当这个类能够通过 反射创建时,进入里面逻辑
if(ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", (ClassLoader)null)) {converters.addAll((new WebMvcConfigurationSupport() {public List<HttpMessageConverter<?>> defaultMessageConverters() {// 然后这里会调用父类的方法获取默认的消息转换器,父类就是WebMvcConfigurationSupportreturn super.getMessageConverters();}}).defaultMessageConverters());} else {converters.addAll((new RestTemplate()).getMessageConverters());}this.reorderXmlConvertersToEnd(converters);return converters;}// WebMvcConfigurationSupport类里面的getMessageConvertersprotected final List<HttpMessageConverter<?>> getMessageConverters() {if (this.messageConverters == null) {this.messageConverters = new ArrayList();this.configureMessageConverters(this.messageConverters);// 系统加载时,肯定是空的,然后会进入里面逻辑if (this.messageConverters.isEmpty()) {this.addDefaultHttpMessageConverters(this.messageConverters);}this.extendMessageConverters(this.messageConverters);}return this.messageConverters;}// 这里面就开始加载各种默认的消息转换器protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {// ByteArrayHttpMessageConverter、StringHttpMessageConverter转换器等等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 var3) {}}messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {messageConverters.add(new AtomFeedHttpMessageConverter());messageConverters.add(new RssChannelHttpMessageConverter());}// 这里会导入Jackson2Xml相关对象,将对象转为xml,Jackson2ObjectMapperBuilder builder;// shouldIgnoreXml这里是yaml可配置的。在WebMvcConfigurationSupport中有做判断private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");if (!shouldIgnoreXml) {// 这里是在WebMvcConfigurationSupport中有一个静态代码块有做判断if (jackson2XmlPresent) {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) {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) {builder = Jackson2ObjectMapperBuilder.smile();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));}if (jackson2CborPresent) {builder = Jackson2ObjectMapperBuilder.cbor();if (this.applicationContext != null) {builder.applicationContext(this.applicationContext);}messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));}}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);// 只要导入了jackson处理xml的包,xml的converter就会自动进来 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);}
无论想要定制springMVC的什么功能,只需要往容器中放入WebMvcConfigurer这个主键,然后在这个组件内定制自己想要的功能即可。
因此,想要定制化内容协商规则,就需要往容器中放入一个自定义WebMvcConfigurer主键,在这个组件内定制化内容协商规则。
// 这个主键有许多的默认方法public interface WebMvcConfigurer {default void configurePathMatch(PathMatchConfigurer configurer) {}default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}default void addFormatters(FormatterRegistry registry) {}default void addInterceptors(InterceptorRegistry registry) {}default void addResourceHandlers(ResourceHandlerRegistry registry) {}default void addCorsMappings(CorsRegistry registry) {}default void addViewControllers(ViewControllerRegistry registry) {}default void configureViewResolvers(ViewResolverRegistry registry) {}default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}// 添加自定义消息转换器,这个方法添加了MessageConverters会把系统自带的消息转换器给覆盖掉default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}// 这个方法是在系统原有的消息转换器上添加,不会覆盖,相当于额外添加消息转换器default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}@Nullabledefault Validator getValidator() {return null;}@Nullabledefault MessageCodesResolver getMessageCodesResolver() {return null;}
}
栗子:
// 添加自定义消息转换器Object2StringConverter到WebMvcConfigurer
@Configuration
public class SpringMvcConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new Object2StringConverter());}};}}
// 自定义消息转换器
public class Object2StringConverter implements HttpMessageConverter<Man> {@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType) {return false;}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {return clazz.isAssignableFrom(Man.class);}@Overridepublic List<MediaType> getSupportedMediaTypes() {return MediaType.parseMediaTypes("application/x-test");}@Overridepublic Man read(Class<? extends Man> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}// 自定义数据转化规则@Overridepublic void write(Man man, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {String data = man.getName() + "-" + man.getAge();outputMessage.getBody().write(data.getBytes());}
}
// 定义接口,接口中必须有@ResponseBody@ResponseBody@GetMapping("/test")public Man getUnsubscribe(){Man man = new Man();man.setName("test");man.setAge(12);return man;}
结果:
原理:
四、参数类型内容协商
以上包括之前几篇博客,讲的都是通过请求头携带的Accept,来获取客户端能够接收的媒体类型。除了这一种方式还有一种是通过参数类型来告诉服务器,客户端能够接收什么样的媒体类型。
另外一种需要手动开启,才会出现。yaml文件添加
spring:mvc:contentnegotiation:favor-parameter: true
也就是说format=json或format=xml
http://localhost:8080/test?format=json
http://localhost:8080/test?format=xml
这种方式只支持这两种,其他类型就不支持了。 如果想要支持其他媒体类型,需要添加自定义内容协商策略。
第一种方法
@Configuration
public class SpringMvcConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {// 这里需要把之前系统默认支持的类型写上去,不然会被覆盖了// 但是这种自定义会有问题,问题就是会把之前从请求头获取媒体类型的组件给覆盖了,只剩下从参数中获得组件了Map<String, MediaType> mediaTypes = new HashMap<>();mediaTypes.put("json",MediaType.APPLICATION_JSON);mediaTypes.put("xml",MediaType.APPLICATION_XML);mediaTypes.put("tt",MediaType.parseMediaType("application/x-test"));ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);configurer.strategies(Collections.singletonList(strategy));}@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new Object2StringConverter());}};}}
出现问题就是会把之前从请求头获取媒体类型的组件给覆盖了,只剩下从参数中获得组件了
这会造成请求头无论穿什么接收类型,最终都是返回json格式
@Configuration
public class SpringMvcConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {// 这里需要把之前系统默认支持的类型写上去,不然会被覆盖了// 但是这种自定义会有问题,问题就是会把之前从请求头获取媒体类型的组件给覆盖了,只剩下从参数中获得组件了Map<String, MediaType> mediaTypes = new HashMap<>();mediaTypes.put("json",MediaType.APPLICATION_JSON);mediaTypes.put("xml",MediaType.APPLICATION_XML);mediaTypes.put("tt",MediaType.parseMediaType("application/x-test"));ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);// 这里需要把基于请求头获取媒体类型的组件添加,不然会丢失掉HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();configurer.strategies(Arrays.asList(strategy,headerContentNegotiationStrategy));}@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new Object2StringConverter());}};}}
第二种方法
在yaml中添加如下配置,就会开启,之后也会支持从请求头中获取了
spring:mvc:contentnegotiation:favor-parameter: truemedia-types:tt: application/x-test