响应处理

响应 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>
3、请求头的 Accept 字段由 Http 协议规定,告知服务器本客户端可以接收的数据类型

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>
posted @   半条咸鱼  阅读(64)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示