Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

developframework/kite

Repository files navigation

Kite

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选用一种底层实现技术就行

0. 文档传送

1. 为什么使用Kite?

场景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>

About

json和xml的可配置生成器

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /