响应处理
响应 JSON
1、引入 Web 场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
(1)自动引入 JSON 场景启动器,相当于引入 jackson.jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.7.0</version>
<scope>compile</scope>
</dependency>
(2)处理 JSON 核心依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.13.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.3</version>
<scope>compile</scope>
</dependency>
(3)导入第三方依赖,MessageConverter 即可支持其他类型数据
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");
private static final boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
private static final boolean kotlinSerializationJsonPresent;
//静态判断该类型是否存在
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);
}
//按需添加默认HttpMessageConverters,系统存在该类,则添加
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 (Throwable ex) {
// 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()));
}
}
}
2、只需要添加 @ResponseBody,自动返回 JSON 数据到前端
3、源码
(1)获取返回值
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//invokeForRequest进入目标方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
//若返回值不为null
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
//StringUtils检测字符串,返回值是否存在失败原因的信息
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//returnValueHandlers处理返回值,getReturnValueType获取返回值类型
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
@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;
}
private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
//遍历所有返回值处理器,判断是否为异步
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler instanceof AsyncHandlerMethodReturnValueHandler &&
((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
//若存在,返回true
return true;
}
}
//都不为异步,返回false
return false;
}
}
(2)选择返回值处理器
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
@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);
}
}
(3)处理 JSON 数据的返回值处理器:RequestResponseBodyMethodProcessor
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
//封装原生Request -> inputMessage、Response -> outputMessage
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
//使用消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
(4)AbstractMessageConverterMethodProcessor:消息转换器的方法处理器
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
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());
}
}
}
//选择媒体类型初始为null
MediaType selectedMediaType = null;
//获取内容类型
MediaType contentType = outputMessage.getHeaders().getContentType();
//内容类型中是否有匹配,判断当前响应头中是否已经有确定的媒体类型(MediaType)
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
//若没有匹配
else {
//获取原生Request
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes;
try {
//内容协商,获取客户端支持接收的内容类型,即获取客户端Accept请求头字段
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
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;
}
//对mediaTypesToUse进行排序、不完全去重
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
//遍历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) {
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()));
}
}
//获取客户端支持接收的类型
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
//contentNegotiationManager内容协商管理器
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
//获取服务器支持响应的类型
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass) {
return getProducibleMediaTypes(request, valueClass, null);
}
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);
}
//result存放支持的媒体类型
List<MediaType> result = new ArrayList<>();
//遍历所有消息转换器
for (HttpMessageConverter<?> converter : this.messageConverters) {
//若converter为GenericHttpMessageConverter类型,且targetType不为null
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result);
}
}
(5)ContentNegotiationManager:内容协商管理器
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
//HeaderContentNegotiationStrategy:确定客户端可以接收的内容类型
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
//若媒体类型为模糊查询 */*,则直接下一次循环
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
//若所有媒体类型都为模糊查询,则返回 */*
return MEDIA_TYPE_ALL_LIST;
}
}
(6)HeaderContentNegotiationStrategy:确定客户端可以接收的内容类型
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
//获取请求头Accept字段
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
//若请求头为null,则返回模糊查询 */*
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
}
//String[] -> List<String>
List<String> headerValues = Arrays.asList(headerValueArray);
try {
//List<String> -> List<MediaType>
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
//若mediaTypes为null,则返回模糊查询 */*
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
}
(7)JSON <-> Class 互转的消息转换器:MappingJackson2HttpMessageConverter
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
Class<?> clazz = (object instanceof MappingJacksonValue ?
((MappingJacksonValue) object).getValue().getClass() : object.getClass());
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
//generator生成器
try (JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding)) {
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 ?
objectMapper.writerWithView(serializationView) : 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);
}
}
}
规范
1、返回值处理器接口:HandlerMethodReturnValueHandler
(1)返回值处理器决定 SpringMVC 目标方法的返回值的种类个数
(2)执行流程:返回值处理器调用 supportsReturnType,判断是否支持该类型返回值 -> 若支持,则返回值处理器调用 handleReturnValue 进行处理
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
(3) SpringMVC 所支持的返回值:ModelAndView、Model、View、ResponseEntity、ResponseBodyEmitter、StreamingResponseBody、HttpEntity、HttpHeaders、Callable、DeferredResult、ListenableFuture、CompletionStage、
WebAsyncTask、方法标注 @ModelAttribute、方法标注 @ResponseBody
2、消息处理器接口:HttpMessageConverter
(1)canRead:读取请求,判断是否可将 MediaType 转换为 Class
(2)canWrite:输出响应,判断是否可将 Class 转换为 MediaType
(3)默认消息处理器:ByteArrayHttpMessageConverter(支持 Byte)、StringHttpMessageConverter(支持 String)、ResourceHttpMessageConverter(支持 Resource)、ResourceRegionHttpMessageConverter(支持 ResourceRegion)、
SourceHttpMessageConverter(支持 DOMSource.class、SAXSource.class、StAXSource.class、StreamSource.class、Source.class)、AllEncompassingFormHttpMessageConverter(支持 MultiValueMap)、
MappingJackson2HttpMessageConverter(支持所有类型,直接返回 true)、Jaxb2RootElementHttpMessageConverter(支持注解方式的 xml 处理)
内容协商
1、根据客户端接收能力不同,返回不同媒体类型的数据
2、引入 xml 依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
4、原理(源码参照以上)
(1)判断当前响应头中是否已经有确定的媒体类型 MediaType
(2)获取客户端支持接收的内容类型,即获取客户端 Accept 请求头字段
(3)ContentNegotiationManager:内容协商管理器,默认只有 HeaderContentNegotiationStrategy,即默认使用基于请求头的策略
(4)HeaderContentNegotiationStrategy:请求头内容协商策略,确定客户端可以接收的内容类型,获取客户端 Accept 请求头字段:String[] -> List<String> -> List<MediaType>
(5)获取可生成类型,即服务器可以响应哪些类型
(6)遍历循环所有当前系统的 MessageConverter,查找支持操作该对象的消息转换器,统计消息转换器所支持的媒体类型
(7)遍历匹配可接收类型,和可生产类型,所有匹配媒体类型封装到 List<MediaType> mediaTypesToUse
(8)对 mediaTypesToUse 进行排序、不完全去重
(9)遍历 mediaTypesToUse,第一个媒体类型作为最佳匹配类型,并将其返回
(10)遍历容器底层的所有 HttpMessageConverter,选择最佳消息转换器,进行类型转化
5、开启基于请求参数方式的内容协商功能
(1)默认为 false
private boolean favorParameter = false;
(2)yaml 配置文件
spring:
mvc:
contentnegotiation:
favor-parameter: true
(3)浏览器访问路径中使用 format 指定接收类型;例:/xxx?format=xml、/xxx?format=json
(4) ContentNegotiationManager 新增 ParameterContentNegotiationStrategy,参数内容优先策略优先于 HeaderContentNegotiationStrategy
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
//参数名为format
private String parameterName = "format";
public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "'parameterName' is required");
this.parameterName = parameterName;
}
public String getParameterName() {
return this.parameterName;
}
@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
return request.getParameter(getParameterName());
}
}
(5)继承父类 MappingMediaTypeFileExtensionResolver 属性 mediaTypes
private final ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<>(64);
(6)ParameterContentNegotiationStrategy 的 mediaTypes 默认键值对:"xml" -> "application/xml"、"json" -> "application/json"
自定义 MessageConverter
1、实现多协议数据兼容
2、@ResponseBody 响应数据,调用 RequestResponseBodyMethodProcessor 处理
3、Processor 处理方法返回值,通过 MessageConverter 处理
4、所有 MessageConverter 可以支持各种媒体类型数据的读写操作
5、内容协商查找最终的 messageConverter
6、消息转换器的两种实现方式
public interface WebMvcConfigurer {
//自定义MessageConverter覆盖默认的消息转换器
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
//自定义MessageConverter作为拓展,追加到默认的消息转换器
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
7、往 Spring 容器直接注入 WebMvcConfigurer
(1)拓展方式示例
(2)类型适配内容协商处理器
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
//重写该方法,会覆盖原先的内容协商协议,包括ParameterContentNegotiationStrategy、HeaderContentNegotiationStrategy
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
//设置指定支持解析的参数,与对应的媒体类型,封装到HashMap
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("custom", MediaType.parseMediaType("application/custom"));
//参数-媒体类型(键值对)封装到参数内容协商策略
ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//重新添加请求头内容协商策略
HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
//参数内容协商策略、请求头内容协商策略,以List形式封装到内容协商配置器
configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy));
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CustomedMessageConverter());
}
}
}
}
8、自定义 MessageConverter 类
(1)实现接口 / 继承父类
(2)自定义 MessageConverter 可读写操作的类型,作为泛型
public interface HttpMessageConverter<T> {
/**
*表明给定的类是否可以被这个转换器读取。
* @param clazz 要测试可读性的类。
* @param mediaType 要读取的媒体类型(如果不指定可以是{@code null})。
* 通常是{@code Content-Type}头的值。
* @return 如果可读,返回true,否则返回false
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* 表示给定的类是否可以被这个转换器写入。
* @param clazz 要测试可写性的类。
* @param mediaType 要写入的媒体类型(如果不指定可以是{@code null})。
* 通常是一个{@code Accept}头的值。
* @return 如果可写,返回true,否则返回false
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* 返回该转换器所支持的媒体类型的列表。该列表可能
* 并非适用于每个可能的目标元素类型,对该方法的调用
* 通常应该通过{@link #canWrite(Class, MediaType)}进行保护。
* canWrite(clazz, null)}。列表中也可以排除只支持某类的MIME类型。
* 只支持某个特定的类。另外,可以使用
* {@link #getSupportedMediaTypes(Class)}获得更精确的列表。
* @return 支持的媒体类型列表
*/
List<MediaType> getSupportedMediaTypes();
/**
* 返回该转换器支持的媒体类型的列表,用于给定的
* 类支持的媒体类型列表。如果这个转换器不支持给定的类,或者只支持给定的类,那么这个列表可能与{@link #getSupportedMediaTypes()}不同。
* 如果转换器不支持给定的类,或者它只支持它的
* 一个媒体类型的子集。
* @param clazz 要检查的类的类型
* @return 给定类别所支持的媒体类型的列表
* @自5.3.4以来
*/
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ? getSupportedMediaTypes() : Collections.emptyList());
}
/**
* 从给定的输入信息中读取一个给定类型的对象,并将其返回。
* @param clazz 要返回的对象的类型。这个类型的对象必须先前已经传递给
* 这个接口的{@link #canRead canRead}方法,它必须返回{@code true}。
* @param inputMessage 要读取的HTTP输入信息
* @return 被转换的对象
* @如果出现I/O错误,则抛出IOException
* @在转换错误的情况下抛出HttpMessageNotReadableException
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
/**
* 将一个给定的对象写到给定的输出信息中。
* @param t 要写到输出消息的对象。这个对象的类型必须是之前已经
* 传递给这个接口的{@link #canWrite canWrite}方法,该方法必须返回{@code true}。
* @param contentType 写入时要使用的内容类型。可以是{@code null}来表示
* 必须使用转换器的默认内容类型。如果不是{@code null},这个媒体类型必须是
* 之前被传递给这个接口的{@link #canWrite canWrite}方法,该方法必须已经
* 返回{@code true}。
* @param outputMessage 要写入的信息
* @throws IOException in case of I/O errors
* @在转换错误的情况下抛出HttpMessageNotWritableException。
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T>
public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> implements GenericHttpMessageConverter<T>
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战