(2.4)、内容协商原理
- 1.判断当前响应头种是否已经有已经确定的媒体类型。MediateType
- 2.获取客户端(浏览器或者PostMan)支持的请求的
Accept头(浏览器)
。通过 contentNegotiationManager内容协商管理器
默认使用基于请求头的策略
- (1).先得到客户端能够接受的所有媒体类型是什么。
- 3.获取服务器能够生产的媒体类型。(
服务器方
) - 4.遍历服务器所有支持的媒体类型 进行 与客户端能够接受的类型进行匹配的操作,选择最佳匹配。
- 5.用支持将对象转为最佳媒体类型的converter,调用它进行转化。
为什么说引入xml的转环包就会被底层接受的原理
WebMvcConfigurationSupport 类下 917行 jackson2XmlPresent
(2.5)、自定义 MessageConverter
实现多协议数据兼容。json、xml、x-guigu
0、@ResponseBody
响应数据出去 调用 RequestResponseBodyMethodProcessor
处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;
package com.jsxs.controller; import com.jsxs.bean.Person; import com.jsxs.bean.Pet; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.text.ParseException; import java.text.SimpleDateFormat; /** * @Author Jsxs * @Date 2023/7/6 10:16 * @PackageName:com.jsxs.controller * @ClassName: ResponseTestController * @Description: TODO * @Version 1.0 */ @Controller @ResponseBody public class ResponseTestController { /** * * @return * @throws ParseException * * @TODO: 1.浏览器请求返回xml文件。2.ajax请求返回json文件。3.硅谷app请求返回自定义文件 * 在以前一个请求完成这项工作这是不可能完成的任务,但是在现在我们有了内容协商我们可以完成这个任务。 * 步骤: 1.添加自定义的MessageConverter进入系统底层。2.系统底层就会统计出所有MessageConverter * 3.进行内容客户端与服务器内容协商匹配。 */ @GetMapping("/test/person") public Person person() throws ParseException { Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2)); return person; } }
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { } } }
首先配置协议转换器
package com.jsxs.convert; import com.jsxs.bean.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; /** * @Author Jsxs * @Date 2023/7/7 12:38 * @PackageName:com.jsxs.convert * @ClassName: GguiGuMessageConverter * @Description: TODO 自定义的消息转换器 * @Version 1.0 */ public class GuiGuMessageConverter implements HttpMessageConverter<Person> { @Override public boolean canRead(Class<?> clazz, MediaType mediaType) { return false; } /** * 条件是什么 * @param clazz * @param mediaType * @return */ @Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); //只有返回的类型是Person类行就能进行读写 } /** * 服务器要统计所有的MessageConverter 都能写哪些内容类型 * * application/x-jsxs * @return */ @Override public List<MediaType> getSupportedMediaTypes() { return MediaType.parseMediaTypes("application/x-jsxs"); } @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.getAge()+";"+person.getPet(); // 写出去 OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); } }
package com.jsxs.config; import com.jsxs.bean.Pet; import com.jsxs.convert.GuiGuMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.StringUtils; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.util.UrlPathHelper; import java.util.List; /** * @Author Jsxs * @Date 2023/7/3 11:13 * @PackageName:com.jsxs.config * @ClassName: WebConfig * @Description: TODO * @Version 1.0 */ @Configuration(proxyBeanMethods = false) // 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写) // 第二种方式: @Configuration +@Bean 重新注入我们的组件 public class WebConfig /*implements WebMvcConfigurer */{ @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); hiddenHttpMethodFilter.setMethodParam("aaaa"); return hiddenHttpMethodFilter; } // @Override // public void configurePathMatch(PathMatchConfigurer configurer) { // UrlPathHelper helper = new UrlPathHelper(); // helper.setRemoveSemicolonContent(false); // configurer.setUrlPathHelper(helper); // } @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer(){ // 配置支持我们的矩阵注解 @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper helper = new UrlPathHelper(); helper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(helper); } // 配置支持我们的自定义converter转换器 @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>() { @Override public Pet convert(String source) { //source 就是页面提交过来的值。只获得过来的值 if (!StringUtils.isEmpty(source)){ // 假如说提交的数据不为空 Pet pet = new Pet(); String[] split = source.split(","); pet.setName(split[0]); // 逗号之前的设置成姓名 pet.setAge(Integer.parseInt(split[1])); return pet; } return null; } }); } // 扩展 内容消息转换器 @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new GuiGuMessageConverter()); } }; } }
上面的内容可以通过 PostMan 进行处理。浏览器因为我们自己设置不了请求头,所以目前测试不了我们自定义的内容协商。
(2.6)、运用参数的方式请求自定义内容协商
我们通过debug的方式进入到了我们浏览器接受的内容协议上,并查看到有两种接收方式,并在请求参数的方式上没有看到 自定义的格式,所以我们要进行自定义的操作。
因为只兼容上面两种 所以我们要进行配置内容协商功能
package com.jsxs.config; import com.jsxs.bean.Pet; import com.jsxs.convert.GuiGuMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.StringUtils; import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.ParameterContentNegotiationStrategy; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.util.UrlPathHelper; import java.util.*; /** * @Author Jsxs * @Date 2023/7/3 11:13 * @PackageName:com.jsxs.config * @ClassName: WebConfig * @Description: TODO * @Version 1.0 */ @Configuration(proxyBeanMethods = false) // 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写) // 第二种方式: @Configuration +@Bean 重新注入我们的组件 public class WebConfig /*implements WebMvcConfigurer */{ @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); hiddenHttpMethodFilter.setMethodParam("aaaa"); return hiddenHttpMethodFilter; } // @Override // public void configurePathMatch(PathMatchConfigurer configurer) { // UrlPathHelper helper = new UrlPathHelper(); // helper.setRemoveSemicolonContent(false); // configurer.setUrlPathHelper(helper); // } @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer(){ // 配置支持我们的矩阵注解 @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper helper = new UrlPathHelper(); helper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(helper); } // 配置支持我们的自定义converter转换器 @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>() { @Override public Pet convert(String source) { //source 就是页面提交过来的值。只获得过来的值 if (!StringUtils.isEmpty(source)){ // 假如说提交的数据不为空 Pet pet = new Pet(); String[] split = source.split(","); pet.setName(split[0]); // 逗号之前的设置成姓名 pet.setAge(Integer.parseInt(split[1])); return pet; } return null; } }); } // 扩展 内容消息转换器 @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new GuiGuMessageConverter()); } // 自定义(重写)内容协商 ⭐⭐⭐ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { // 要求需要为String HashMap<String, MediaType> mediaTypeHashMap = new HashMap<>(); // 配置支持的请求参数 mediaTypeHashMap.put("json",MediaType.APPLICATION_JSON); mediaTypeHashMap.put("xml",MediaType.APPLICATION_XML); mediaTypeHashMap.put("jsxs",MediaType.parseMediaType("application/x-jsxs")); // 支持解析哪些参数对应的哪些媒体类型 -》 参数内容协商支持 ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypeHashMap); // 支持解析哪些参数对应的哪些媒体类型 -》 请求头内容协商支持 (这里通过PostMan进行测试) HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy(); // 真正执行 configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy)); } }; } }
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】