Kite框架实现通过XML文件配置来自定义json和xml格式,大大提升了java生成json和xml字符串的自由性,让开发模块化更加便捷快速。
Kite是一款面向接口编程的框架,目前支持如下主流序列化框架作为核心技术:
- 以Jackson作为核心序列化json
- 以Fastjson作为核心序列化json
- 以Gson作为核心序列化json
- 以Dom4j作为核心序列化xml
JDK11及以上
<dependency> <groupId>com.github.developframework</groupId> <artifactId>kite-jackson</artifactId> <version>${version.kite}</version> </dependency> <dependency> <groupId>com.github.developframework</groupId> <artifactId>kite-fastjson</artifactId> <version>${version.kite}</version> </dependency> <dependency> <groupId>com.github.developframework</groupId> <artifactId>kite-gson</artifactId> <version>${version.kite}</version> </dependency> <dependency> <groupId>com.github.developframework</groupId> <artifactId>kite-dom4j</artifactId> <version>${version.kite}</version> </dependency>
json或xml选用一种底层实现技术就行
场景1:最原始的场景,依靠持久层框架(这里是spring-data-jpa为例)从数据库中查询一条记录并使用springmvc默认的
MappingJackson2HttpMessageConverter序列化成json响应
@RestController @RequestMapping("users") public class UserController { @Autowired private UserRepository userRepository; @GetMapping("{id}") public UserPO findUserDetail(@PathVariable int id) { return userRepository.findById(id); } }
这里的痛点:
- Jackson序列化了UserPO这个和数据库交互的实体类PO(Persist Object),把表的所有字段都做了输出,类似password这种敏感字段都出去了
- 不能重命名字段的名称
- 不能可选的设置null值不参与序列化
- 不能对字段的值进行处理
场景2:这时候你会说那么Jackson、Fastjson框架可以在UserPO里加注解来定义序列化的结果。那么看场景2,UserPO是如下定义的:
@Getter @Setter @Entity @Table(name="user") @JsonInclude(Include.NON_NULL) // 此处加了Jackson的注解用于不序列化null值的字段 public class UserPO { @Id private Integer id; @JsonSerialize(using = MobileEncryptSerializer.class) // 此处加了Jackson的注解用于加密手机号 @Column(nullable=false, length=11, unique=true) private String mobile; @JsonProperty("username") // 此处加了Jackson的注解用于重命名字段名称 @Column(nullable=false, length=20) private String name; @JsonIgnore // 此处加了Jackson的注解用于忽略该字段的序列化 @Column(nullable=false, length=32) private String password; @CreateDate private String createTime; }
// 手机号的加密器,把中间4位转为* public class MobileEncryptSerializer extends JsonSerializer<String> { @Override public void serialize(String mobile, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(mobile.substring(0, 3) + "****" + mobile.substring(7)); } }
这里的痛点:
- 很明显,持久层框架JPA的注解和Jackson的注解混在一起,代码非常混乱
@JsonInclude(Include.NON_NULL)只能设置在类上,代表全部字段的null策略,不能精细控制到某个字段@JsonSerialize、@JsonProperty和@JsonIgnore之类的注解一旦加上了就适用在任何场景的序列化上。做不到有时候需要显示,有时候不需要显示
场景3:针对场景2,你会说那么把UserPO的数据导入到多个DTO类,把注解加在DTO类上层次更加清晰点,或者使用
@JsonView注解适用于不同的场景下的响应需求,那么看场景3:
@Data public class UserDTO { // 平台管理查询 public interface ForManage {} private int id; @JsonSerialize(using = MobileEncryptSerializer.class) private String mobile; @JsonProperty("username") private String name; @JsonView(ForManage.class) // 平台管理查询的时候需要显示password字段 private String password; private String createTime; }
@RestController @RequestMapping("users") public class UserController { @Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository; @GetMapping("{id}") @JsonView(UserDTO.ForManage.class) // 这里选择了使用什么视图 public UserDTO findUserDetail(@PathVariable int id) { UserPO userPO = userRepository.findById(id); // 这里采用mapstruct这种搬数据的框架,如果自己写set语句那么又是一长串的样板代码 return userMapper.toDTO(userPO) } }
这里的痛点:
- 需要新建一个传输对象做数据迁移操作
- 新建了两个不必要的类UserDTO和UserDTO.ForManage,如果不同的场景更多,那么需要建更多的类
场景4:多个数组数据无法插接到一起:
@RestController @RequestMapping("users") public class UserController { @Autowired private UserRepository userRepository; @Autowired private AddressRepository addressRepository; @GetMapping("list") public List<UserDTO> findUserAndAddressList() { List<UserPO> users = userRepository.findAll(); List<AddresPO> addresses = addressRepository.findAll(); // 下面想把每个user的多个address收货地址插接到一起,那么得自己来干这件事,并输出到List<UserDTO> return null; } }
最终想得到类似下面结构的json
[
{
"id": 1,
"mobile": "177****7777",
"name": "小张",
"addresses": [
{
"userId": 1,
"county": "北京-北京市-朝阳区",
"location": "xxxxxx"
},
{
"userId": 1,
"county": "北京-北京市-海淀区",
"location": "yyyyyy"
}
]
},
{
"id": 2,
"mobile": "188****8888",
"name": "小李",
"addresses": [
{
"userId": 2,
"county": "上海-上海市-黄浦区",
"location": "zzzzzz"
}
]
}
]场景5:如果固定的json格式则需要一个通用的实体类来封装,比如最常见的类似下面的实体类:
@Getter @Setter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class Result<T> { private final boolean success; private final String message; private final T data; public static <T> Result<T> ok(T data) { return new Result(true, null, data); } public static <T> Result<T> fail(String message) { return new Result(false, message, null); } }
@RestController @RequestMapping("users") public class UserController { @GetMapping public Result<UserPO> find() { try { UserPO user = // 干点查询 return Result.ok(dto); } catch(Exception e) { return Result.fail(e.getMessage()); } } }
输出下面的格式:
{
"success": true,
"message": null,
"data": {
"user": {
// 数据内容
}
}
}使用Kite都能解决以上痛点
场景1、场景2和场景3的解决
@Controller @KiteNamespace("kite-user") @RequestMapping("users") public class UserController { @Autowired private UserRepository userRepository; @TemplateId("user-detail") @GetMapping("{id}") public DataModel findUserDetail(@PathVariable int id) { return DataModel .singleton("user", userRepository.findById(id)) // 可以有条件控制分支 .putData("needPassword", true) // 声明手机号的加密逻辑 .putConverter("mobileEncryptConverter", (KiteConverter<String, String>) mobile -> mobile.substring(0, 3) + "****" + mobile.substring(7)); } }
<template id="user-detail" data="user"> <property data="id"/> <!-- 可以对字段处理 --> <property data="mobile" converter="mobileEncryptConverter"/> <!-- 可以重命名字段 --> <property data="name" alias="username"/> <!-- 可以有条件选择是否需要哪些字段 --> <if condition="needPassword"> <property data="password"/> </if> <!-- 可以精细控制某个字段null是不是显示 --> <property-date data="createTime" null-hidden="true"/> </template>
完全没有新建任何类,也没有给UserPO加入任何注解
场景4的解决
@Controller @KiteNamespace("kite-user") @RequestMapping("users") public class UserController { @Autowired private UserRepository userRepository; @Autowired private AddressRepository addressRepository; @TemplateId("user-list") @GetMapping("list") public DataModel findUserAndAddressList() { List<UserPO> users = userRepository.findAll(); List<AddresPO> addresses = addressRepository.findAll(); // 这里啥都不用干,直接把数据放入即可 return DataModel .singleton("users", users) .putData("addresses", addresses) // 声明如何关联 .putRelFunction("rel", (RelFunction<UserPO, AddressPO>)(u, ui, a, ai) -> u.getId().equals(a.getUserId())) } }
<template id="user-list" data="users"> <!-- 可以直接引用之前声明过的片段,无需重复定义 --> <include id="user-detail"/> <!-- 一对多的关联,自动匹配插接数据 --> <relevance data="#addresses" rel="rel"> <property data="userId"/> <property data="county"/> <property data="location"/> </relevance> </template>
场景5的解决,不需要建类
@RestController @KiteNamespace("kite-user") @RequestMapping("users") public class UserController { @TemplateId("user-extend") @GetMapping public DataModel find() { try { UserPO user = // 干点查询 return DataModel.singleton("success", true).putData("user", user); } catch(Exception e) { return DataModel.singleton("success", false).putData("message", e.getMessage()); } } }
<!-- 公共的父级格式 --> <fragment id="common-parent"> <property data="success"/> <property data="message" null-hidden="true"/> <!-- 虚拟层结构,并没有data这个数据 --> <object-virtual alias="data"> <!-- 锚点,所有的子片段插接于此 --> <slot/> </object-virtual> </fragment> <!-- 子片段声明继承自哪个父片段 --> <template id="user-extend" data="user" extend="common-parent"> <include id="user-detail"/> </template>