当前位置: 首页 > article >正文

springboot系列--web相关知识探索七

一、前言

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或xml
                        if (((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() {
                    // 然后这里会调用父类的方法获取默认的消息转换器,父类就是WebMvcConfigurationSupport
                    return super.getMessageConverters();
                }
            }).defaultMessageConverters());
        } else {
            converters.addAll((new RestTemplate()).getMessageConverters());
        }

        this.reorderXmlConvertersToEnd(converters);
        return converters;
    }







// WebMvcConfigurationSupport类里面的getMessageConverters
    protected 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) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

 栗子:

// 添加自定义消息转换器Object2StringConverter到WebMvcConfigurer 
@Configuration
public class SpringMvcConfig {

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

}
// 自定义消息转换器
public class Object2StringConverter implements HttpMessageConverter<Man> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Man.class);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-test");
    }

    @Override
    public Man read(Class<? extends Man> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    // 自定义数据转化规则
    @Override
    public 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 {

        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {

                @Override
                public 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));

                }

                @Override
                public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                    converters.add(new Object2StringConverter());
                }
            };
        }

}

 

 

 出现问题就是会把之前从请求头获取媒体类型的组件给覆盖了,只剩下从参数中获得组件了

 

这会造成请求头无论穿什么接收类型,最终都是返回json格式 

 

@Configuration
public class SpringMvcConfig {

        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {

                @Override
                public 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));

                }

                @Override
                public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                    converters.add(new Object2StringConverter());
                }
            };
        }

}

第二种方法

在yaml中添加如下配置,就会开启,之后也会支持从请求头中获取了

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      media-types:
        tt: application/x-test

 


http://www.kler.cn/a/387554.html

相关文章:

  • Java反序列化之CommonsCollections2链的学习
  • 【大数据学习 | kafka高级部分】kafka中的选举机制
  • 分布式----Ceph部署(上)
  • 深入解析Hadoop:大数据处理的基石
  • 【韩老师零基础30天学会Java 】06章 数组、排序和查找
  • ssm093基于Java Web的毕业生就业状况管理系统设计与实现+jsp(论文+源码)_kaic
  • 使用 pd.ExcelWriter 创建多工作表 Excel 文件的详细教程
  • JAVA中的string和stringbuffer
  • SQL进阶技巧:如何计算复合增长率?
  • 如何在Python中实现一个简单的搜索引擎:从零开始的指南
  • Vue中父组件通过v-model向子组件传对象参数
  • 图像识别算法优化:提升识别精度与速度
  • 记一次文件包含刷题(伪协议篇)
  • Leetcode 买卖股票的最佳时机 Ⅱ
  • 思考:linux Vi Vim 编辑器的简明原理,与快速用法之《 7 字真言 》@ “鱼爱返 说 温泉哦“ (**)
  • 华为云计算知识总结——及案例分享
  • kaggle学习 eloData项目(2)-数据清洗
  • C/C++中预处理器指令有哪些,举例说明其用途。
  • 2.索引:SQL 性能分析详解
  • Intel AMT技术在服务器硬件监控中的应用与解读
  • C语言--结构体详解
  • Ubuntu下如何管理多个ssh密钥
  • OSPF总结
  • Django 详细入门介绍
  • 使用Rust实现http/https正向代理
  • 动态规划 —— dp 问题-买卖股票的最佳时机含手续费