Serialization(序列化): 将java对象以一连串的字节码保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。
deserialization(反序列化): 将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化。
★ 自定义JSON的序列化器和反序列化器
▲ 注册自定义序列化器和反序列化器有两种方式:- 方式1:利用Jackson的模块机制来注册自定义序列化器和反序列化器。- 方式2:利用Spring Boot提供的@JsonComponent来注册自定义序列化器和反序列化器。第一种方式是Jackson原生的注册方式,一般不推荐。推荐使用第二种方式。
★ 使用@JsonComponent注册序列化器或反序列化器
Spring Boot提供了@JsonComponent注解来注册自定义自定义序列化器和反序列化器,
该注解有两种使用方式:
-方式1: 直接使用 @JsonComponent 注解修饰JsonSerializer、JsonDeserializer或KeyDeserializer实现类,这些实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。@JsonComponent 注解修饰 JsonSerializer,就是自定义 JSON 序列化器@JsonComponent 注解修饰 JsonDeserializer,就是自定义 JSON 反序列化器-方式2:使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。这种方式相当于将序列化器和反序列化器都定义在一个外部类中,这种方式具有更好的内聚性,通常推荐用方式2。
内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类
Token定义
Token是Fastjson中定义的json字符串的同类型字段,即"{“、”["、数字、字符串等,用于分隔json字符串不同字段。
例如,{“姓名”:“张三”,“年龄”:“20”}是一个json字符串,在反序列化之前,需要先将其解析为
{ 、 姓名、 :、 张三、 ,、 年龄、 :、 20、 }这些字段的Token流,随后再根据class反序列化为响应的对象。
在进行Token解析之前,json字符串对程序而言只是一个无意义的字符串。需要将json字符串解析为一个个的Token,并以Token为单位解读json数据。
代码演示:
自定义序列化和反序列化器的代码
package cn.ljh.app.custom;import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;import java.io.IOException;//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{//在该类中可定义自定义的序列化器和反序列化器//postman 用get请求时走这个方法//自定义序列化器,这个就是内部类public static class MySerializer extends JsonSerializer<Book>{@Overridepublic void serialize(Book book, JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException{//在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串jsonGenerator.writeStartObject(); //输出 { 这个花括号 , 输出对象用这个括号
// jsonGenerator.writeStartArray(); //输出 [ 这个中括号 , 输出数值用这个括号jsonGenerator.writeNumberField("id", book.getId());//将book对象的name属性,序列化为title 属性jsonGenerator.writeStringField("title", book.getName());jsonGenerator.writeNumberField("price", book.getPrice());jsonGenerator.writeStringField("author", book.getAuthor());
// jsonGenerator.writeEndArray(); // 输出这个 ] 中括号jsonGenerator.writeEndObject(); //输出这个 } 花括号}}//postman 用post的请求访问到这个方法//自定义反序列化器,这个就是内部类public static class MyDeserializer extends JsonDeserializer<Book>{//这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象@SneakyThrows@Overridepublic Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException{Book book = new Book();//JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token//token:一个花括号也叫token,一个符号隔开一个内容就叫tokenJsonToken jsonToken = jsonParser.getCurrentToken();//只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取while (!jsonToken.equals(JsonToken.END_OBJECT)){//这个循环的作用,是去保证要找到代表 Field Name 的 Token//如果当前读取的Token 不是字段名if (!jsonToken.equals(JsonToken.FIELD_NAME)){jsonToken = jsonParser.nextToken();//读取下一个tokencontinue;}//只要程序进到此处,就一定是读取到 field name 这种字段token//从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名String fieldName = jsonParser.getCurrentName();//读取属性名之后,下一个Token就是属性值了JsonToken valueToken = jsonParser.nextToken();//获取当前token的值String value = jsonParser.getText();//对于要逻辑处理的属性,可以在这里进行判断处理if (fieldName.equals("title")){if (!value.startsWith("《")){value = "《" + value;}if (!value.endsWith("》")){value += "》";}//完成了对Book对象的name属性的设置book.setName(value);} else if (fieldName.equals("price")){double price = Double.parseDouble(value);//把从json或xml格式的价格数据拿出来,然后打折set进去book.setPrice(price * 0.5);} else{//对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法BeanUtils.getPropertyDescriptor(Book.class, fieldName).getWriteMethod().invoke(book, value);}//继续向下读取下一个 TokenjsonToken = jsonParser.nextToken();}return book;}}}
测试:
序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象
前端发送 get 请求,会走序列化方法。
http://localhost:8080/books
(不知道是如何定义访问的方法是走序列化还是反序列化方法)
get 请求是获取对象,序列化方法,把对象序列化成json格式的数据返回
序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象
用 post 请求,会走反序列化方法,
发送的请求是 json 格式的数据,所以走反序列化方法,将数据恢复成对象
不太懂如何怎么应用这个自定义的序列化和反序列化器。
完整代码:
Book
BookController
package cn.ljh.app.controller;import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;/** GET /books/{id} - (获取数据) 获取指定id的图书* GET /books?参数 -(获取数据) 获取符合查询参数的图书* GET /books -(获取数据) 获取所有图书* POST /books -(添加数据) 添加图书* PUT /books/{id} -(更新数据) 更新指定ID的图书* DELETE /books/{id} -(删除数据) 删除指定ID的图书* DELETE /books?参数 -(删除数据) 删除符合指定参数的图书** Restful处理方法的返回值通常都应该使用HttpEntity或ResponseEntity。**/@RequestMapping("/books")
@RestController
public class BookController
{//有参构造器进行依赖注入private BookService bookService;public BookController(BookService bookService){this.bookService = bookService;}//根据id查看图书@GetMapping("/{id}")public ResponseEntity<Book> viewBookById(@PathVariable Integer id){Book book = bookService.getBookById(id);//参数1:响应数据体 参数2:需要添加的响应头,没有就给个null 参数3:响应码 , OK 代表 200return new ResponseEntity<>(book, null, HttpStatus.OK);}//查看所有图书@GetMapping("")public ResponseEntity<List<Book>> viewBooks(){List<Book> allBooks = bookService.getAllBooks();return new ResponseEntity<>(allBooks, null, HttpStatus.OK);}//添加图书@PostMapping("")public ResponseEntity<Book> addBook(@RequestBody Book book){Book b = bookService.addOrUpdateBook(book);//HttpStatus.CREATED 代表返回的状态码为 201return new ResponseEntity<>(b, null, HttpStatus.CREATED);}//根据id更新图书信息@PutMapping("/{id}")public ResponseEntity<Book> updateBookById(@PathVariable Integer id, @RequestBody Book book){book.setId(id);Book b = bookService.addOrUpdateBook(book);return new ResponseEntity<>(b, null, HttpStatus.OK);}//根据id删除图书@DeleteMapping("/{id}")public ResponseEntity<Book> deleteBookById(@PathVariable Integer id){Book book = bookService.deleteBookById(id);return new ResponseEntity<>(book, null, HttpStatus.OK);}}
BookService
package cn.ljh.app.service;import cn.ljh.app.domain.Book;import java.util.List;public interface BookService
{//根据id查看图书Book getBookById(Integer id);//查看所有图书List<Book> getAllBooks();//添加/修改图书Book addOrUpdateBook(Book book);//根据id删除图书Book deleteBookById(Integer id);}
BookServiceImpl
package cn.ljh.app.service.impl;import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.stereotype.Service;import java.util.*;
import java.util.concurrent.ConcurrentHashMap;@Service
public class BookServiceImpl implements BookService
{//创建一个线程安全的map集合存数据,假设为数据库static Map<Integer, Book> bookDB = new ConcurrentHashMap<>();static int nextId = 1;//初始化数据库的数据static{bookDB.put(nextId, new Book(nextId++, "火影忍者", 120, "岸本"));bookDB.put(nextId, new Book(nextId++, "七龙珠", 121, "鸟山明"));}//根据id查看图书@Overridepublic Book getBookById(Integer id){if (id != null){Book book = bookDB.get(id);if (book!=null){return book;}}throw new RuntimeException("根据id查看图书失败!");}//查看所有图书@Overridepublic List<Book> getAllBooks(){//获取map中的所有数据Collection<Book> mapBooks = bookDB.values();//强转List<Book> books = new ArrayList<>(mapBooks);return books;}//添加/修改图书@Overridepublic Book addOrUpdateBook(Book book){if (book.getId() != null){//修改//map的key是唯一的,所以map里面有这个key的话,直接把原来的value覆盖掉bookDB.put(book.getId(),book);return book;}else {//新增//为新增的图书设置idbook.setId(nextId);//book添加完之后,这个id才会自增bookDB.put(nextId++,book);return book;}}//根据id删除图书@Overridepublic Book deleteBookById(Integer id){Book book = bookDB.remove(id);return book;}
}
CustomSeDe
package cn.ljh.app.custom;import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;import java.io.IOException;//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{//在该类中可定义自定义的序列化器和反序列化器//postman 用get请求时走这个方法//自定义序列化器,这个就是内部类public static class MySerializer extends JsonSerializer<Book>{@Overridepublic void serialize(Book book, JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException{//在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串jsonGenerator.writeStartObject(); //输出 { 这个花括号 , 输出对象用这个括号//jsonGenerator.writeStartArray(); //输出 [ 这个中括号 , 输出数值用这个括号jsonGenerator.writeNumberField("id", book.getId());//将book对象的name属性,序列化为title 属性jsonGenerator.writeStringField("title", book.getName());jsonGenerator.writeNumberField("price", book.getPrice());jsonGenerator.writeStringField("author", book.getAuthor());//jsonGenerator.writeEndArray(); // 输出这个 ] 中括号jsonGenerator.writeEndObject(); //输出这个 } 花括号}}//postman 用post的请求访问到这个方法//自定义反序列化器,这个就是内部类public static class MyDeserializer extends JsonDeserializer<Book>{//这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象@SneakyThrows@Overridepublic Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException{Book book = new Book();//JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token//token:一个花括号也叫token,一个符号隔开一个内容就叫tokenJsonToken jsonToken = jsonParser.getCurrentToken();//只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取while (!jsonToken.equals(JsonToken.END_OBJECT)){//这个循环的作用,是去保证要找到代表 Field Name 的 Token//如果当前读取的Token 不是字段名if (!jsonToken.equals(JsonToken.FIELD_NAME)){jsonToken = jsonParser.nextToken();//读取下一个tokencontinue;}//只要程序进到此处,就一定是读取到 field name 这种字段token//从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名String fieldName = jsonParser.getCurrentName();//读取属性名之后,下一个Token就是属性值了JsonToken valueToken = jsonParser.nextToken();//获取当前token的值String value = jsonParser.getText();//对于要逻辑处理的属性,可以在这里进行判断处理if (fieldName.equals("title")){if (!value.startsWith("《")){value = "《" + value;}if (!value.endsWith("》")){value += "》";}//完成了对Book对象的name属性的设置book.setName(value);} else if (fieldName.equals("price")){double price = Double.parseDouble(value);//把从json或xml格式的价格数据拿出来,然后打折set进去book.setPrice(price * 0.5);} else{//对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法BeanUtils.getPropertyDescriptor(Book.class, fieldName).getWriteMethod().invoke(book, value);}//继续向下读取下一个 TokenjsonToken = jsonParser.nextToken();}return book;}}}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version></parent><groupId>cn.ljh</groupId><artifactId>CustomSeDe</artifactId><version>1.0.0</version><name>CustomSeDe</name><properties><java.version>11</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 添加 Jackson format XML , 用于对象与XML之间的转换 --><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>