自定义转换器&处理JSON&内容协商

1.自定义转换器

1.1基本介绍

  1. SpringBoot 在响应客户端请求时,将提交的数据封装成对象时,使用了内置的转换器,也就是自动帮我们封装对象。springboot 自带了124个转换器,可以实现大部分的类型间的转换。
  2. SpringBoot 也支持自定义转换器。但当前台发送请求传递的参数使用内置的转换器不能转换时,这时就需要写一个自定义的数据类型转换器,我们只需要实现 Converter 接口的 convert 方法即可

1.2应用案例

演示自定义转换器的使用。

(1)save.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>save</title>
</head>
<body>
<form action="/saveMonster" method="post">
编号:<input name="id" value="10001"/><br/>
姓名:<input name="name" value="齐天大圣"/><br/>
年龄:<input name="age" value="888"/><br/>
婚否:<input name="isMarried" value="false"/><br/>
生日:<input name="birth" value="1456/12/12"/><br/>
<!--使用自定义转换器关联car,value字符串整体添加,使用逗号间隔-->
坐骑:<input name="car" value="避水金晶兽,666.6"/><br/>
<input type="submit" value="保存"/>
</form>
</body>
</html>

(2)自定义转换器(String-->Car)

package com.li.config;

import com.li.bean.Car;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /**
* @author 李
* @version 1.0
* (proxyBeanMethods = false)
* 1.表示启用Lite模式,保证修饰的配置类中,每个@Bean方法被调用多少次返回的组件都是新创建的,
* 是多例对象,是非代理方式。
* 2.proxyBeanMethods 在调用 @Bean 方法时才生效,因此需要先获取BeanConfig 组件,再调用方法
*/
@Configuration(proxyBeanMethods = false)//设置为设置类
public class WebConfig {
//注入bean-WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 1.在 addFormatters()方法中添加一个自定义的转换器
* 2.自定义转换器要完成的功能是 String -> Car
* 3.增加的转换器会注册到 converters 容器中
* 4.converters 底层结构是 ConcurrentHashMap,默认内置了124个转换器
*/
registry.addConverter(new Converter<String, Car>() {
//<sourceType,targetType>
//匿名内部类
@Override
public Car convert(String source) {//source即传入的字符串
//加入转换的业务代码
if (!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] strings = source.split(",");
car.setName(strings[0]);
car.setPrice(Double.parseDouble(strings[1]));
return car;
}
return null;
}
});
//还可以增加更多的转换器
}
};
}
}

(3)控制器

Monster和Car是级联对象,Car为Monster对象的属性。

//处理添加Monster的方法
@PostMapping("/saveMonster")
@ResponseBody
public String saveMonster(Monster monster) {
System.out.println("monster=" + monster);
return "success";
}

(4)浏览器提交表单,后台输出如下:

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

package com.li.controller;

import com.li.bean.Car;
import com.li.bean.Monster;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.Date; /**
* @author 李
* @version 1.0
*/
@Controller
public class ResponseController {
//编写方法,以json格式返回数据
@GetMapping("/get/monster")
@ResponseBody
public Monster getMonster() {
Monster monster = new Monster();
monster.setId(199);
monster.setName("孙悟空");
monster.setAge(23);
monster.setIsMarried(false);
monster.setBirth(new Date());
Car car = new Car();
car.setName("奔驰");
car.setPrice(20000.0);
monster.setCar(car);
return monster;
}
}

浏览器访问该方法,返回如下:

为什么SpringBoot可以将Monster对象以 JSON格式返回呢?

它的底层仍然用到了一个转换器:AbstractJackson2HttpMessageConverter

其中一个重要的方法如下:

返回数据的格式是按照你设置的contentType类型。如果没有指定,默认为json格式。

//参数Object object就是控制器方法返回的对象类型,如Monster
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//当控制器方法返回的时候,获取返回对象的contentType,一般为application/json
MediaType contentType = outputMessage.getHeaders().getContentType();
//获取编码,一般为utf-8
JsonEncoding encoding = this.getJsonEncoding(contentType);
Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody()); try {//生成一个UTF8JsonGenerator对象
JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);
Throwable var10 = null; try {//对返回的数据类型进行一系列处理
this.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 = this.getJavaType(type, (Class)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);
} //UTF8JsonGenerator对象处理返回的数据
objectWriter.writeValue(generator, value);
this.writeSuffix(generator, object);
generator.flush();
} catch (Throwable var26) {
var10 = var26;
throw var26;
} finally {
if (generator != null) {
if (var10 != null) {
try {
generator.close();
} catch (Throwable var25) {
var10.addSuppressed(var25);
}
} else {
generator.close();
}
} } } catch (InvalidDefinitionException var28) {
throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
} catch (JsonProcessingException var29) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
}
}

3.内容协商

3.1基本说明

内容协商:服务端和请求端协商决定最终返回什么格式的内容。客户端发送请求的时候可以告知服务器,自己希望对方返回的数据格式列表,而服务器的接口也有能支持响应的格式列表,最终返回的结果会根据这两个类型列表,找到一种两边都能支持的类型返回,如果找不到合适的类型,则报错。

简单来说就是:根据客户端接收能力不同,SpringBoot 返回不同媒体类型的数据

比如:

  • 客户端 Http 请求 Accept: application/xml 则返回 xml 数据
  • 客户端 Http 请求 Accept: application/json 则返回 json 数据

例子1:使用postman测试

(1)使用postman发送Http请求,在此期间服务器的代码不变,根据请求头不同,返回的数据格式也会不同

返回json格式:

返回xml格式:

SpringBoot 默认支持返回 Json数据,不支持返回xml数据,所以需要导入jackson-dataformat-xml

<!--引入处理xml的依赖-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

例子2:使用浏览器测试

浏览器不能指定Accept属性,我们在浏览器发出请求,发现返回的数据为xml格式:

这是因为浏览器的Accept中指定了多种媒体类型,如下:

  1. 支持接收html,xhtml+xml,xml格式类型的权重为0.9
  2. 支持接收image和 */* [所有类型] 格式的权重为0.8
  3. 所以服务器会优先返回xhtml+xml格式

3.2问题

如上,客户端通过Http请求的Accept来指定能接收的媒体类型。那么服务端的接口是怎么响应这个类型的呢?

内容协商原理

内容协商原理:

  1. 判断当前响应头中是否已经有确定的媒体类型
  2. 获取客户端Accept请求头字段
  3. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象
  4. 找到支持操作当前操作对象的converter,把converter支持的媒体类型统计出来
  5. 进行内容协商得到最佳匹配媒体类型

3.3注意事项

Postman可以通过修改Accept的值来返回不同的数据格式。对于浏览器来说,我们无法修改其Accept的值,这时如果要指定返回json格式,怎么办呢?

解决方案:开启支持基于请求参数的内容协商功能

(1)修改application.yml

spring:
mvc:
contentnegotiation:
favor-parameter: true #开启基于请求参数的内容协商,默认不开启

(2)在浏览器请求的时候带上format参数,此时返回的就是指定的格式了

注意:参数format的值是规定好的,在开启请求参数的内容协商功能后,SpringBoot底层ParameterContentNegotiationStrategy会通过format来接收参数,然后返回对应的媒体类型/数据格式,因此format的值也要是SpringBoot能处理的才行。

当然format这个属性名本身也可以通过配置文件修改:


day08-自定义转换器&处理JSON&内容协商的更多相关文章

  1. Loadrunner请求自定义的http(json)文件and参数化

    Loadrunner请求自定义的http(json)文件and参数化      研究啦好些天这个东西啦 终于出来答案啦 嘿嘿 给大家分享一下 : 请求自定义的http文件用函数:web_custom_ ...

  2. spring mvc 自定义转换器

    <!-- 注册转化器 --> <mvc:annotation-driven conversion-service="conversionService" /> ...

  3. Struts2 请求数据的自动封装 及 自定义转换器类

    请求数据自动封装: 实现原理:使用了参数拦截器.struts-default.xml中 <interceptor name="params" class="com. ...

  4. Flask自定义转换器,实现路由匹配正则表达式参数

    Flask框架动态路由实现参数传递和Django框架有类似之处,但是相比于Django框架,Flask实现复杂的参数就需要自己自定义转换器来实现了,而不能向Django那样直接使用正则表达式 # 路由 ...

  5. struts2自定义转换器

    Struts2自定义类型转换器分为局部类型转换器和全局类型转换器 (1)局部类型转换器 如果页面传来一个参数reg.action?birthday=2010-11-12到后台action,然后属性用d ...

  6. Spring -- 自定义转换器

    Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到 ConversionServiceFactoryBean 中: Converter<S,T> ...

  7. flask自定义转换器

    根据具体的需求,有些时候是需要用到正则来灵活匹配URL,但是Flask的路由匹配机制是不能直接在路由里直接写正则的,这时候就需要使用转换器! Flask的默认转换器: DEFAULT_CONVERTE ...

  8. Retrofit 2.0基于OKHttp更高效更快的网络框架 以及自定义转换器

    时间关系,本文就 Retrofit 2.0的简单使用 做讲解  至于原理以后有空再去分析 项目全面.简单.易懂  地址: 关于Retrofit 2.0的简单使用如下:  https://gitee.c ...

  9. 一、数据库表中字段的增删改查,二、路由基础.三、有名无名分组.四、多app共存的路由分配.五、多app共存时模板冲突问题.六、创建app流程.七、路由分发.八、路由别名,九、名称空间.十、反向解析.十一、2.x新特性.十二、自定义转换器

    一.数据库表中字段的增删改查 ''' 直接在modules中对字段进行增删改查 然后在tools下点击Run manage.py Task执行makemigrations和migrate 注意在执行字 ...

  10. springmvc 自定义view支持json和jsonp格式数据返回

    1.如果controlloer上用@ResponseBody注解,则用<mvc:message-converter>里面配置的json解析器进行解析 <mvc:annotation- ...

随机推荐

  1. [OC] 链式语法

    我们新建了一个类,叫做 OJClass (这可以是 ViewController,UIView,NSObject 等各种类型的类,这里我们把它以UIView进行举例) 现在我们想要用链式语法的方式来设 ...

  2. 删除Windows10资源管理器多余的入口

    ///// 删除3D对象 定位到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\Nam ...

  3. 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流

    接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用. 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此 ...

  4. 如何保证RabbitMQ的消息按照顺序执行???

    可以采用单线程的消费保证消息的顺序性.对消息进行编号,1,2,3,4--消费时按照编号的顺序去消费消息.这样就可以保证消息 按照一定顺序执行.

  5. kubectl使用方法及常用命令小结

    Kubectl 是一个命令行接口,用于对 Kubernetes 集群运行命令.kubectl 在 $HOME/.kube 目录中寻找一个名为 config 的文件. kubectl安装方法详见:htt ...

  6. Educational Codeforces Round 3 个人总结A-D

    Educational Codeforces Round 3 A. USB Flash Drives 降序排序后,贪心,甚至不会爆longlong void solve() { int n,m; ci ...

  7. Spring系列之资源-11

    目录 `Resource` 内置`Resource`实现 `UrlResource` `ClassPathResource` `FileSystemResource` `ServletContextR ...

  8. postgresql Extract 函数的使用

    Extract 属于 SQL 的 DML(即数据库管理语言)函数,同样,InterBase 也支持 Extract,它主要用于从一个日期或时间型的字段内抽取年.月.日.时.分.秒数据,因此,它支持其关 ...

  9. 面向对象ooDay8

    精华笔记: 接口: 是一种数据类型(引用类型) 由interface定义 只能包含常量和抽象方法(所有数据默认都是常量,所有方法默认都是抽象的) 接口不能被实例化 接口是需要被实现/继承的,实现/派生 ...

  10. github使用流程

    前期硬件准备工作(电脑相关配置): 1.下载git软件,傻瓜式安装 https://git-scm.com/download/win 2.设置你的用户名称与邮件地址 git config --glob ...