目录
5.处理@ResponseBody 注解的RequestResponseBodyMethodProcessor
8.MappingJackson2HttpMessageConverter
1.关键代码AbstractMessageConverterMethodProcessor
4.遍历服务器所有MessageConverter,看谁支持操作这个对象(Person)
一、springmvc响应json
1. web场景自动引入了json场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
引入web场景会自动引入json,json默认是使用jackson解析的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
2.使用方式
(1)@ResponseBody
在方法上加@ResponseBody
(2)@RestController
实际上@RestController=@Controller+@ResponseBody
将实体类转成json格式,给前端自动返回json数据。
二、springmvc响应json数据原理
1.springmvc请求处理逻辑
springmvc处理逻辑参考博文:
springBoot-springMVC请求处理原理_A_art_xiang的博客-CSDN博客
2.返回值的处理
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) { // 设置请求参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) { // 设置响应值处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer); // 执行目标方法
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 处理返回值,利用所有的返回值处理器来处理返回值。
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
// org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 找能处理返回值的返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);// 返回值处理
}
// org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { // 循环遍历所有的返回值处理器
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) { // 如果返回值类型找到对应的返回值处理器,就返回该处理器
return handler;
}
}
return null;
}
3.返回值处理器
有许多返回值处理器
返回值处理器也是一个接口,HandlerMethodReturnValueHandler。
supportsReturnType来判断是否能处理该返回值类型。
handleReturnValue来真正处理返回值。
(1)、返回值处理器判断是否支持这种类型返回值 supportsReturnType
(2)、返回值处理器调用 handleReturnValue 进行处理
(3)、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
4.SpringMVC到底支持哪些返回值
ModelAndView
Model
View // 视图
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody // 流式数据
HttpEntity // 不能为RequestEntity
HttpHeaders
Callable// 支持异步
DeferredResult // 支持异步
ListenableFuture// 支持异步
CompletionStage
WebAsyncTask
方法有 @ModelAttribute 且为对象类型的
方法有@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;(自定义实体类响应json数据,使用此返回值处理器)
5.处理@ResponseBody 注解的RequestResponseBodyMethodProcessor
// 处理返回值
// org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 利用 MessageConverters 进行处理 将数据写为json
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;
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());
}
// 判断返回值是否是资源类型(stream流数据)
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 = getAcceptableMediaTypes(request); // 获取浏览器能接收的内容类型
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 (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
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);
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// // 循环所有的消息转换器,SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
for (HttpMessageConverter<?> converter : this.messageConverters) {
// 得到MappingJackson2HttpMessageConverter可以将对象写为json
// 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
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(this.allSupportedMediaTypes);
}
}
6.内容协商
内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,请求头Accept)
7.HttpMessageConverter
(1)HttpMessageConverter实际是一个接口
HttpMessageConverter左右: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
canRead:是否能读(媒体类型参数)
canWrite:是否能写(媒体类型参数)
getSypportedMediaTypes:判断是否支持该类型的返回值
(2)默认的MessageConverter
0 - 只支持Byte类型的返回值
1 - 只支持String类型的返回值
2 - 只支持String类型的返回值
3 - 只支持Resource类型的返回值
4 - 只支持ResourceRegion类型的返回值
5 - 支持DOMSource.class \ SAXSource.class \ StAXSource.class \StreamSource.class \Source.class类型的返回值
6 - 只支持MultiValueMap类型的返回值
7 - 直接返回true
8 - 直接返回true
9 - 支持注解方式xml处理的
8.MappingJackson2HttpMessageConverter
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
// org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
// org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
writeInternal(t, type, outputMessage);
// 最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
outputMessage.getBody().flush();
}
}
9.总结
标注@ResponseBody -> 使用RequestResponseBodyMethodProcessor处理 -> 查找对应的messageConverter处理返回值。
messageConverter可以处理多种返回值(不止局限于自定义的实体类,但是一定要加上@ResponseBody注解)
三、初识内容协商
1.什么是内容协商
内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,请求头Accept)。
根据客户端接收能力不同,返回不同媒体类型的数据。
2.springboot响应xml报文
(1)引入支持xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
(2)服务器会按照请求头来判断返回的内容格式
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
如果写application/json会返回json报文,如果写application/xml就会返回xml报文。
四、内容协商的原理
同上,标注@ResponseBody -> 使用RequestResponseBodyMethodProcessor处理 -> 查找对应的messageConverter处理返回值。
1.关键代码AbstractMessageConverterMethodProcessor
// AbstractMessageConverterMethodProcessor的writeWithMessageConverters
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,只要没加拦截器处理一般情况下都没有
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();
// 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
// 底层其实就是使用原生request,调用getHeaderValues("Accept")
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
// 找到支持操作Person的converter,把converter支持的媒体类型统计出来。(详见上面)
// 导入了jackson处理xml的包,xml的converter就会自动进来
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
// 最终发现,客户端需要【application/xml】。服务端可以提供10种服务【json、xml】
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
// 内容协商,双重for循环,找出最佳匹配媒体类型。最终将匹配的媒体类型放到mediaTypesToUse
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
// 若无匹配,会抛异常
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
// 排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// 选中的媒体类型只会有一个,有匹配的就break。
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);
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 找Converter,看谁能将对象转成需要的媒体类型(xml)
// 用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
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(this.allSupportedMediaTypes);
}
}
2.各种服务器端支持的Converts
后四个分别支持对象转json、对象转xml。
其中对象转xml的因为导入了jackson的xml支持的包,会自动出现。
3.获取客户端(PostMan、浏览器)支持接收的内容类型
底层其实就是使用原生request,调用getHeaderValues("Accept")
// org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
}
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 ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
4.遍历服务器所有MessageConverter,看谁支持操作这个对象(Person)
// org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type)
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
5.服务端支持的10种处理数据类型(MediaType)
6.权重优先匹配原则
浏览器q=0.9,权重为0.9,没有写支持json,而*/*权重为0.8,所以优先会返回xml格式。
7.开启基于请求参数的内容协商功能
(1)使用
浏览器内容协商优先支持xml,可以开启使用请求参数作为内容协商。
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
(2)原理
开启配置之后,内容协商管理器会注册一个使用参数的内容协商策略,参数名称就是format,format的值就支持xml和json。
底层实际上是使用request.getParameter("format");获取请求参数。
五、自定义MessageConverter
1.功能
实现多协议数据兼容。json、xml、test-测试报文类型
(1)、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
(2)、Processor 处理方法返回值。通过 MessageConverter 处理
(3)、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
(4)、内容协商找到最终的 messageConverter;
2.实现
(1)添加自定义的MessageConverter
import com.cxf.model.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* 自定义的Converter,操作Person类型的数据
*/
public class TestMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false; // 不需要支持读
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class); // 是Person类型就可以写
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
* application/test
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
// 把字符串解析成媒体类型集合
return MediaType.parseMediaTypes("application/test");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName()+"|"+person.getAge()+"|"+person.getBirth();
//写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
(2)在配置类中注册刚写的Converter.
//WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
// 扩展MessageConverters
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new TestMessageConverter());
}
};
}
3.测试
4.使用参数方式
(1)加配置
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
(2)配置类加配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration(proxyBeanMethods = false)
public class WebConfig{
//WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
/**
* 自定义内容协商策略,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map<String, MediaType> mediaTypes
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("test",MediaType.parseMediaType("application/test")); // 服务端可以产生test类型的数据
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
parameterStrategy.setParameterName("ff"); // ff=test
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy(); // 放一个基于请求头的,都可以使用
configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}
// 扩展MessageConverters
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new TestMessageConverter());
}
};
}
}
(3)测试一下吧