Serialization(序列化): 将java对象以一连串的字节码保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。
deserialization(反序列化): 将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化。
▲ 注册自定义序列化器和反序列化器有两种方式: - 方式1:利用Jackson的模块机制来注册自定义序列化器和反序列化器。 - 方式2:利用Spring Boot提供的@JsonComponent来注册自定义序列化器和反序列化器。 第一种方式是Jackson原生的注册方式,一般不推荐。推荐使用第二种方式。
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{ @Override public 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 { //这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象 @SneakyThrows @Override public Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { Book book = new Book(); //JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token //token:一个花括号也叫token,一个符号隔开一个内容就叫token JsonToken jsonToken = jsonParser.getCurrentToken(); //只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取 while (!jsonToken.equals(JsonToken.END_OBJECT)) { //这个循环的作用,是去保证要找到代表 Field Name 的 Token //如果当前读取的Token 不是字段名 if (!jsonToken.equals(JsonToken.FIELD_NAME)) { jsonToken = jsonParser.nextToken();//读取下一个token continue; } //只要程序进到此处,就一定是读取到 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); } //继续向下读取下一个 Token jsonToken = jsonParser.nextToken(); } return book; } } }
序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象
前端发送 get 请求,会走序列化方法。
http://localhost:8080/books
(不知道是如何定义访问的方法是走序列化还是反序列化方法)
get 请求是获取对象,序列化方法,把对象序列化成json格式的数据返回
序列化输出,将对象输出成 json 或者是 xml 字符串
反序列化,就是将 json 或者是 xml 字符串 恢复成对象
用 post 请求,会走反序列化方法,
发送的请求是 json 格式的数据,所以走反序列化方法,将数据恢复成对象
不太懂如何怎么应用这个自定义的序列化和反序列化器。
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 ResponseEntityviewBookById(@PathVariable Integer id) { Book book = bookService.getBookById(id); //参数1:响应数据体 参数2:需要添加的响应头,没有就给个null 参数3:响应码 , OK 代表 200 return new ResponseEntity<>(book, null, HttpStatus.OK); } //查看所有图书 @GetMapping("") public ResponseEntity > viewBooks() { List
allBooks = bookService.getAllBooks(); return new ResponseEntity<>(allBooks, null, HttpStatus.OK); } //添加图书 @PostMapping("") public ResponseEntity addBook(@RequestBody Book book) { Book b = bookService.addOrUpdateBook(book); //HttpStatus.CREATED 代表返回的状态码为 201 return new ResponseEntity<>(b, null, HttpStatus.CREATED); } //根据id更新图书信息 @PutMapping("/{id}") public ResponseEntity 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 deleteBookById(@PathVariable Integer id) { Book book = bookService.deleteBookById(id); return new ResponseEntity<>(book, null, HttpStatus.OK); } }
package cn.ljh.app.service; import cn.ljh.app.domain.Book; import java.util.List; public interface BookService { //根据id查看图书 Book getBookById(Integer id); //查看所有图书 ListgetAllBooks(); //添加/修改图书 Book addOrUpdateBook(Book book); //根据id删除图书 Book deleteBookById(Integer id); }
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 MapbookDB = new ConcurrentHashMap<>(); static int nextId = 1; //初始化数据库的数据 static { bookDB.put(nextId, new Book(nextId++, "火影忍者", 120, "岸本")); bookDB.put(nextId, new Book(nextId++, "七龙珠", 121, "鸟山明")); } //根据id查看图书 @Override public Book getBookById(Integer id) { if (id != null) { Book book = bookDB.get(id); if (book!=null){ return book; } } throw new RuntimeException("根据id查看图书失败!"); } //查看所有图书 @Override public List getAllBooks() { //获取map中的所有数据 Collection mapBooks = bookDB.values(); //强转 List books = new ArrayList<>(mapBooks); return books; } //添加/修改图书 @Override public Book addOrUpdateBook(Book book) { if (book.getId() != null){ //修改 //map的key是唯一的,所以map里面有这个key的话,直接把原来的value覆盖掉 bookDB.put(book.getId(),book); return book; }else { //新增 //为新增的图书设置id book.setId(nextId); //book添加完之后,这个id才会自增 bookDB.put(nextId++,book); return book; } } //根据id删除图书 @Override public Book deleteBookById(Integer id) { Book book = bookDB.remove(id); return book; } }
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{ @Override public 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 { //这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象 @SneakyThrows @Override public Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { Book book = new Book(); //JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token //token:一个花括号也叫token,一个符号隔开一个内容就叫token JsonToken jsonToken = jsonParser.getCurrentToken(); //只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取 while (!jsonToken.equals(JsonToken.END_OBJECT)) { //这个循环的作用,是去保证要找到代表 Field Name 的 Token //如果当前读取的Token 不是字段名 if (!jsonToken.equals(JsonToken.FIELD_NAME)) { jsonToken = jsonParser.nextToken();//读取下一个token continue; } //只要程序进到此处,就一定是读取到 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); } //继续向下读取下一个 Token jsonToken = jsonParser.nextToken(); } return book; } } }
4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.5 cn.ljh CustomSeDe 1.0.0 CustomSeDe 11 UTF-8 org.springframework.boot spring-boot-starter-web com.fasterxml.jackson.dataformat jackson-dataformat-xml org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok