ToB企服应用市场:ToB评测及商务社交产业平台

标题: day08-自定义转换器&处理JSON&内容协商 [打印本页]

作者: 滴水恩情    时间: 2023-3-20 21:36
标题: day08-自定义转换器&处理JSON&内容协商
自定义转换器&处理JSON&内容协商

1.自定义转换器

1.1基本介绍

1.2应用案例

演示自定义转换器的使用。
(1)save.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>save</title>
  6. </head>
  7. <body>
  8. <form action="/saveMonster" method="post">
  9.     编号:<input name="id" value="10001"/><br/>
  10.     姓名:<input name="name" value="齐天大圣"/><br/>
  11.     年龄:<input name="age" value="888"/><br/>
  12.     婚否:<input name="isMarried" value="false"/><br/>
  13.     生日:<input name="birth" value="1456/12/12"/><br/>
  14.    
  15.     坐骑:<input name="car" value="避水金晶兽,666.6"/><br/>
  16.     <input type="submit" value="保存"/>
  17. </form>
  18. </body>
  19. </html>
复制代码
(2)自定义转换器(String-->Car)
  1. package com.li.config;
  2. import com.li.bean.Car;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.convert.converter.Converter;
  5. import org.springframework.format.FormatterRegistry;
  6. import org.springframework.util.ObjectUtils;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8. /**
  9. * @author 李
  10. * @version 1.0
  11. * (proxyBeanMethods = false)
  12. * 1.表示启用Lite模式,保证修饰的配置类中,每个@Bean方法被调用多少次返回的组件都是新创建的,
  13. *        是多例对象,是非代理方式。
  14. * 2.proxyBeanMethods 在调用 @Bean 方法时才生效,因此需要先获取BeanConfig 组件,再调用方法
  15. */
  16. @Configuration(proxyBeanMethods = false)//设置为设置类
  17. public class WebConfig {
  18.     //注入bean-WebMvcConfigurer
  19.     @Bean
  20.     public WebMvcConfigurer webMvcConfigurer() {
  21.         return new WebMvcConfigurer() {
  22.             @Override
  23.             public void addFormatters(FormatterRegistry registry) {
  24.                 /**
  25.                  * 1.在 addFormatters()方法中添加一个自定义的转换器
  26.                  * 2.自定义转换器要完成的功能是 String -> Car
  27.                  * 3.增加的转换器会注册到 converters 容器中
  28.                  * 4.converters 底层结构是 ConcurrentHashMap,默认内置了124个转换器
  29.                  */
  30.                 registry.addConverter(new Converter<String, Car>() {
  31.                     //<sourceType,targetType>
  32.                     //匿名内部类
  33.                     @Override
  34.                     public Car convert(String source) {//source即传入的字符串
  35.                         //加入转换的业务代码
  36.                         if (!ObjectUtils.isEmpty(source)) {
  37.                             Car car = new Car();
  38.                             String[] strings = source.split(",");
  39.                             car.setName(strings[0]);
  40.                             car.setPrice(Double.parseDouble(strings[1]));
  41.                             return car;
  42.                         }
  43.                         return null;
  44.                     }
  45.                 });
  46.                 //还可以增加更多的转换器
  47.             }
  48.         };
  49.     }
  50. }
复制代码
(3)控制器
Monster和Car是级联对象,Car为Monster对象的属性。
  1. //处理添加Monster的方法
  2. @PostMapping("/saveMonster")
  3. @ResponseBody
  4. public String saveMonster(Monster monster) {
  5.     System.out.println("monster=" + monster);
  6.     return "success";
  7. }
复制代码
(4)浏览器提交表单,后台输出如下:
  1. monster=Monster(id=10001, name=齐天大圣, age=888, isMarried=false, birth=Sun Dec 12 00:00:00 CST 1456, car=Car(name=避水金晶兽, price=666.6))
复制代码
可以看到服务器成功获取到表单数据,并将car表单项的value值转换为Car对象的属性值。
1.3注意事项

不同转换器通过key值区分,key=[源类型->目标类型]

如果实现的两个自定义转换器的 key 值相同(即源类型和目标类型相同),则在注入容器时,根据注入的顺序,后一个转换器会覆盖前一个转换器!
2.处理JSON

SpringBoot 支持返回 JSON 格式的数据,在启用 WEB 开发场景时,已经引入了相关的依赖:spring-boot-starter-json。
例子-使用@ResponseBody处理返回 json
  1. package com.li.controller;
  2. import com.li.bean.Car;
  3. import com.li.bean.Monster;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.ResponseBody;
  7. import java.util.Date;
  8. /**
  9. * @author 李
  10. * @version 1.0
  11. */
  12. @Controller
  13. public class ResponseController {
  14.     //编写方法,以json格式返回数据
  15.     @GetMapping("/get/monster")
  16.     @ResponseBody
  17.     public Monster getMonster() {
  18.         Monster monster = new Monster();
  19.         monster.setId(199);
  20.         monster.setName("孙悟空");
  21.         monster.setAge(23);
  22.         monster.setIsMarried(false);
  23.         monster.setBirth(new Date());
  24.         Car car = new Car();
  25.         car.setName("奔驰");
  26.         car.setPrice(20000.0);
  27.         monster.setCar(car);
  28.         return monster;
  29.     }
  30. }
复制代码
浏览器访问该方法,返回如下:
为什么SpringBoot可以将Monster对象以 JSON格式返回呢?
它的底层仍然用到了一个转换器:AbstractJackson2HttpMessageConverter
其中一个重要的方法如下:
返回数据的格式是按照你设置的contentType类型。如果没有指定,默认为json格式。
  1. //参数Object object就是控制器方法返回的对象类型,如Monster
  2. protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
  3.         //当控制器方法返回的时候,获取返回对象的contentType,一般为application/json
  4.     MediaType contentType = outputMessage.getHeaders().getContentType();
  5.     //获取编码,一般为utf-8
  6.     JsonEncoding encoding = this.getJsonEncoding(contentType);
  7.     Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
  8.     ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
  9.     Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
  10.     OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
  11.     try {//生成一个UTF8JsonGenerator对象
  12.         JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);
  13.         Throwable var10 = null;
  14.         try {//对返回的数据类型进行一系列处理
  15.             this.writePrefix(generator, object);
  16.             Object value = object;
  17.             Class<?> serializationView = null;
  18.             FilterProvider filters = null;
  19.             JavaType javaType = null;
  20.             if (object instanceof MappingJacksonValue) {
  21.                 MappingJacksonValue container = (MappingJacksonValue)object;
  22.                 value = container.getValue();
  23.                 serializationView = container.getSerializationView();
  24.                 filters = container.getFilters();
  25.             }
  26.             if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
  27.                 javaType = this.getJavaType(type, (Class)null);
  28.             }
  29.             ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();
  30.             if (filters != null) {
  31.                 objectWriter = objectWriter.with(filters);
  32.             }
  33.             if (javaType != null && javaType.isContainerType()) {
  34.                 objectWriter = objectWriter.forType(javaType);
  35.             }
  36.             SerializationConfig config = objectWriter.getConfig();
  37.             if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
  38.                 objectWriter = objectWriter.with(this.ssePrettyPrinter);
  39.             }
  40.             //UTF8JsonGenerator对象处理返回的数据
  41.             objectWriter.writeValue(generator, value);
  42.             this.writeSuffix(generator, object);
  43.             generator.flush();
  44.         } catch (Throwable var26) {
  45.             var10 = var26;
  46.             throw var26;
  47.         } finally {
  48.             if (generator != null) {
  49.                 if (var10 != null) {
  50.                     try {
  51.                         generator.close();
  52.                     } catch (Throwable var25) {
  53.                         var10.addSuppressed(var25);
  54.                     }
  55.                 } else {
  56.                     generator.close();
  57.                 }
  58.             }
  59.         }
  60.     } catch (InvalidDefinitionException var28) {
  61.         throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
  62.     } catch (JsonProcessingException var29) {
  63.         throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
  64.     }
  65. }
复制代码
3.内容协商

3.1基本说明

内容协商:服务端和请求端协商决定最终返回什么格式的内容。客户端发送请求的时候可以告知服务器,自己希望对方返回的数据格式列表,而服务器的接口也有能支持响应的格式列表,最终返回的结果会根据这两个类型列表,找到一种两边都能支持的类型返回,如果找不到合适的类型,则报错。
简单来说就是:根据客户端接收能力不同,SpringBoot 返回不同媒体类型的数据
比如:
例子1:使用postman测试
(1)使用postman发送Http请求,在此期间服务器的代码不变,根据请求头不同,返回的数据格式也会不同
返回json格式:
返回xml格式:
SpringBoot 默认支持返回 Json数据,不支持返回xml数据,所以需要导入jackson-dataformat-xml
  1. <dependency>
  2.     <groupId>com.fasterxml.jackson.dataformat</groupId>
  3.     <artifactId>jackson-dataformat-xml</artifactId>
  4. </dependency>
复制代码
例子2:使用浏览器测试
浏览器不能指定Accept属性,我们在浏览器发出请求,发现返回的数据为xml格式:
这是因为浏览器的Accept中指定了多种媒体类型,如下:
3.2问题

如上,客户端通过Http请求的Accept来指定能接收的媒体类型。那么服务端的接口是怎么响应这个类型的呢?
内容协商原理
内容协商原理:
3.3注意事项

Postman可以通过修改Accept的值来返回不同的数据格式。对于浏览器来说,我们无法修改其Accept的值,这时如果要指定返回json格式,怎么办呢?
解决方案:开启支持基于请求参数的内容协商功能
(1)修改application.yml
  1. spring:
  2.   mvc:
  3.     contentnegotiation:
  4.       favor-parameter: true #开启基于请求参数的内容协商,默认不开启
复制代码
(2)在浏览器请求的时候带上format参数,此时返回的就是指定的格式了
注意:参数format的值是规定好的,在开启请求参数的内容协商功能后,SpringBoot底层ParameterContentNegotiationStrategy会通过format来接收参数,然后返回对应的媒体类型/数据格式,因此format的值也要是SpringBoot能处理的才行。
当然format这个属性名本身也可以通过配置文件修改:

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4