Kite框架构建于jackson和dom4j框架之上,实现通过XML文件配置来自定义json和xml格式,大大提升了java生成json和xml字符串的自由性,让开发模块化更加便捷快速。
JDK11及以上
maven
<dependency>
<groupId>com.github.developframework</groupId>
<artifactId>kite-core</artifactId>
<version>${version.kite}</version>
</dependency>
一个最简单的kite使用示例:
KiteFactory kiteFactory = new KiteFactory("/kite/kite-demo.xml");
DataModel dataModel = new DataModel();
dataModel.putData("sayHello", "Hello Kite!");
JsonProducer jsonProducer = kiteFactory.getJsonProducer();
// 生成json
String json = jsonProducer.produce(dataModel, "kite-demo", "first-view");
System.out.println(json);
// 生成xml
XmlProducer xmlProducer = kiteFactory.getXmlProducer();
String xml = xmlProducer.produce(dataModel, "kite-demo", "first-view");
System.out.println(xml);
你需要一份Kite XML配置,位置在上述声明的/kite/kite-demo.xml:
<kite-configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://github.com/developframework/kite/schema"
xsi:schemaLocation="
https://github.com/developframework/kite/schema kite-configuration.xsd">
<template-package namespace="kite-demo">
<template id="first-view" xml-root="xml">
<property data="sayHello" />
</template>
</template-package>
</kite-configuration>
运行结果:
{"sayHello":"Hello Kite!"}
<xml><say-hello>Hello Kite!</say-hello></xml>
com.github.developframework.kite.data.DataModel
接口是Kite框架的数据模型。用于装载需要在json视图中渲染的数据或函数接口实现,数据由键值对构成。接口提供存入和取出数据的方法。
存取数据范例:
DataModel dataModel = new DataModel();
dataModel.putData("sayHello", "Hello Kite!");
Optional<Object> value = dataModel.getData("sayHello");
value.ifPresent(System.out::println);
com.github.developframework.expression.Expression
类是kite框架从DataModel中提取数据的表达式。不论dataModel存的是java实体类还是Map对象都可以使用表达式取值。
范例:
student
你可以从DataModel对象内取得名为student的对象#student
你可以从DataModel对象内 强制从根路径 取得名为student的对象student.name
你可以从DataModel对象内取得名为student的对象的name属性值students[0]
你可以从DataModel对象内取得名为students的数组内的第1个元素student[0].name
你可以从DataModel对象内取得名为students的数组内的第1个元素的name属性值
Expression
的详细使用请查看独立项目expression
com.github.developframework.kite.core.KiteFactory
类是Kite框架的构建工厂。使用Kite框架的第一步就是建立该对象。
建立该对象需要提供配置文件路径的字符串,多份配置文件可以采用字符串数组。
final String[] configs = {"config1.xml", "config2.xml"};
KiteFactory kiteFactory = new KiteFactory(configs);
com.github.developframework.kite.core.KiteConfiguration
类为Kite框架的总配置文件,可以从KiteFactory中得到该对象。
KiteConfiguration kiteConfiguration = kiteFactory.getKiteConfiguration();
com.github.developframework.kite.core.JsonProducer
接口是json字符串建造类,执行一次生成json字符串的操作需要构建该对象。JsonProducer由KiteFactory生成。
该对象提供三个构建json字符串的方法:
String produce(DataModel dataModel, String namespace, String templateId, boolean isPretty);
返回json字符串
- isPretty=true时可以美化json
String produce(DataModel dataModel, String namespace, String templateId);
返回json字符串,不美化
void outputJson(JsonGenerator jsonGenerator, DataModel dataModel, String namespace, String templateId, boolean isPretty);
将json输出到ObjectMapper的JsonGenerator对象
com.github.developframework.kite.core.XmlProducer
接口是xml字符串建造类,执行一次生成xml字符串的操作需要构建该对象。XmlProducer由KiteFactory生成。
该对象提供三个构建xml字符串的方法:
String produce(DataModel dataModel, String namespace, String templateId, boolean isPretty);
返回json字符串
- isPretty=true时可以美化xml
String produce(DataModel dataModel, String namespace, String templateId);
返回json字符串,不美化
void outputXml(Writer writer, DataModel dataModel, String namespace, String templateId, boolean isPretty);
将xml输出到Writer
com.github.developframework.kite.core.element.Template
类,一个Template类的实例代表一种视图模板。它由namespace
和id
唯一确定。可以通过以下方法得到Template实例:
Template template = kiteConfiguration.extractTemplate("namespace", "id");
com.github.developframework.kite.core.element.TemplatePackage
类,一个TemplatePackage实例是一个命名空间,可以装载若干个Template实例。推荐将Template按功能有序存放于TemplatePackage。通过以下方法得TemplatePackage对象:
TemplatePackage templatePackage = kiteConfiguration.getTemplatePackageByNamespace("namespace");
Kite框架的所有异常类。
异常 | 说明 |
---|---|
KiteException | kite顶级异常 |
ConfigurationSourceException | 配置源异常 |
TemplateUndefinedException | template未定义异常 |
TemplatePackageUndefinedException | templatePackage未定义异常 |
KiteParseXmlException | 配置文件解析错误异常 |
LinkSizeNotEqualException | 使用link功能时数组大小不相等异常 |
ResourceNotUniqueException | 资源定义不唯一异常 |
InvalidArgumentsException | 无效的参数异常 |
DataUndefinedException | data没有定义在DataModel异常 |
Kite configuration 文档的结构如下:
<kite-configuration>
<template-package namespace="">
<template id="">
<!-- 定义视图内容 -->
</template>
<template id="">
<!-- 定义视图内容 -->
</template>
<!-- 其它template -->
</template-package>
<!-- 其它template-package -->
</kite-configuration>
在<kite-configuration>
节点中你可以配置任意数量的<template-package>
,代表不同的模板包,在<template-package>
节点上你必须声明命名空间namespace
属性,并且namespace
是唯一的,不然会抛出ResourceNotUniqueException
。
在每个<template-package>
节点中你可以配置任意数量的<template>
。每个<template>
即代表了某一种json格式的视图,在<template>
节点你必须声明id属性,并且id必须是唯一的,不然会抛出ResourceNotUniqueException
。
Kite configuration文档不是唯一的,Kite框架允许你拥有多份的Kite configuration配置文档,文档的加载顺序不分先后。
基本型标签
<template>
<object>
<array>
<property>
<this>
<prototype>
json
<xml-attribute>
功能型标签
<include>
<link>
<relevance>
<object-virtual>
<property-ignore>
<extend-port>
<if>
、<else>
<switch>
、<case>
、<default>
拓展型标签
-
<property-date>
-
<property-unixtimestamp>
-
<property-boolean>
-
<property-enum>
enum
当你需要声明一个json格式模板时,你将会使用到<template>
标签。
<template id="" data="" for-class="" converter=""></template>
属性 | 功能 | 是否必须 |
---|---|---|
id | 声明模板编号,在命名空间中唯一 | 是 |
data | 取值表达式 | 否 |
for-class | 声明data表达式指向的对象类型 | 否 |
extend | 声明继承的kite和端口,格式为namespace.id:port(namespace不填时默认为当前namespace) | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
map | 仅当data指代的数据为数组或List时有效。MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 |
xml-root | 生成xml时的根节点名称 | 否 |
xml-item | 生成xml时,子节点数组项的节点名称 | 否 |
当你需要在json中构建一个对象结构时,你将会使用到<object>
标签。详见4.1.节
<object data="" alias="" for-class="" null-hidden="true" converter=""></object>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
for-class | 声明data表达式指向的对象类型 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
当你需要在json中构建一个数组结构时,你将会使用到<array>
标签。详见4.6.节
<array data="" alias="" for-class="" null-hidden="true" converter=""></array>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
for-class | 声明data表达式指向的对象类型 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
map | MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 |
xml-item | 生成xml时,子节点数组项的节点名称 | 否 |
limit | 取前若干个元素 | 否 |
comparator | Comparator比较器接口实现类表达式 | 否 |
null-empty | true时表示表达式取的值为null时设为空数组,默认为false | 否 |
<array>
标签可以没有子标签,这时表示数组为基本类型数组。
当你需要在json中构建一个普通属性结构时, 你将会使用到<property>
标签。
<property data="" alias="" converter="" null-hidden="true"/>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
指代节点本身值
<this alias="" converter=""/>
属性 | 功能 | 是否必须 |
---|---|---|
alias | 别名,你可以重新定义显示名 | 是 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
使用Jackson原型实体构建结构, 你将会使用到<prototype>
标签。详见4.7.节
<prototype data="" alias="" converter="" null-hidden="true"/>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
使用json字符串构建结构, 你将会使用到<json>
标签。
<json data="" alias="" converter="" null-hidden="true"/>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
在输出xml时,提供配置xml节点的属性。
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
Kite框架提供模块化设计json结构视图的功能。在一个<template>
标签中你可以采用<include>
标签来导入其它的<template>
的结构内容,从而实现模块化单元分解。详见5.3.1.节
<include id="" namespace=""/>
属性 | 功能 | 是否必须 |
---|---|---|
id | 需要导入的template id | 是 |
namespace | template的命名空间,不填默认为当前命名空间 | 否 |
该标签用于实现一对一链接对象功能。详见5.4.1.节。
<link data="" alias="" for-class="" null-hidden="true"></link>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式,data必须指代一个List或array类型的对象 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
for-class | 声明data表达式指向的对象类型 | 否 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
该标签用于实现一对多关联功能。详见5.4.2.节。
<relevance data="" alias="" rel="" null-hidden="true" null-empty="true"></relevance>
属性 | 功能 | 是否必须 |
---|---|---|
data | 取值表达式,data必须指代一个List或array类型的对象 | 是 |
alias | 别名,你可以重新定义显示名 | 否 |
rel | 关联判定器全限定类名或expression表达式 | 是 |
null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 |
map | MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 |
null-empty | true时表示表达式取的值为null时设为空数组,默认为false | 否 |
该标签用于虚拟对象结构。详见5.2.节
<object-virtual alias=""></object-virtual>
属性 | 功能 | 是否必须 |
---|---|---|
alias | 别名 | 是 |
忽略属性,需结合for-class
属性使用。详见4.5.节
<property-ignore name=""/>
属性 | 功能 | 是否必须 |
---|---|---|
name | 类中忽略的属性名称 | 是 |
此标签位置作为子<template>
的接入位置。详见5.3.2节
<extend-port port-name=""/>
属性 | 功能 | 是否必须 |
---|---|---|
port-name | 端口名称 | 是 |
分支结构标签。详见5.5.1节
<if condition="">
</if>
<else>
</else>
属性 | 功能 | 是否必须 |
---|---|---|
condition | 条件接口实现类全名或Expression表达式 | 是 |
分支结构标签。详见5.5.2节
<switch check-data="">
<case test=""></case>
<case test=""></case>
<default></default>
</switch>
属性 | 功能 | 是否必须 |
---|---|---|
check-data | 取值表达式 | 否 |
test | CaseTestFunction接口实现类表达式 | 是 |
该标签拓展于<property>
,针对时间日期类型,使用pattern
属性来格式化日期时间。详见4.3.1.节
拓展属性 | 功能 | 是否必须 |
---|---|---|
pattern | 格式化模板字符串,不填默认为"yyyy-MM-dd HH:mm:ss" | 否 |
支持的时间日期类型:
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- (JDK8) java.time.LocalDate
- (JDK8) java.time.LocalDateTime
- (JDK8) java.time.LocalTime
- (JDK8) java.time.Instant
该标签拓展于<property>
,针对时间日期类型,可以将时间日期类型转化为unix时间戳格式显示。可支持的时间日期类型同<property-date>
。详见4.3.2.节
该标签拓展于<property>
,可以将数字类型(short、int、long)变为boolean型,非0值为true,0值为false。详见4.3.3节
该标签拓展于<property>
,可以将值映射成另一个固定值。详见4.3.4节
模型声明(以下各小节示例代码均使用这些模型实体类):
// 学生类 Student.java
@Data
public class Student {
// 编号
private int id;
// 学生名称
private String name;
// 班级ID
private int classId;
// 出生日期
private Date birthday;
public Student(int id, String name, int classId, String birthday) {
this.id = id;
this.name = name;
this.classId = classId;
if(birthday != null) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
this.birthday = dateFormat.parse(birthday);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
// 账户类 Account.java 一个学生对应有一个账户
@Data
@AllArgsConstructor
public class Account {
private String username;
private String password;
}
// 班级类 SchoolClass.java 一个班级对应多个学生
@Data
@AllArgsConstructor
public class SchoolClass {
private int id;
private String className;
}
<!-- /kite/kite-student.xml -->
<!-- 忽略kite-configuration -->
<template-package namespace="kite-student">
<template id="student-detail" data="student">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property data="birthday"/>
</template>
</template-package>
// Application.java main()
KiteFactory factory = new KiteFactory("/kite/kite-student.xml");
JsonProducer jsonProducer = factory.getJsonProducer();
DataModel dataModel = new DataModel();
Student peter = new Student(1, "Peter", 1, "1995-01-01");
dataModel.putData("student", peter);
String json = jsonProducer.produce(dataModel, "kite-student", "student-detail");
System.out.println(json);
执行结果:
{"id":1,"name":"Peter","classId":1,"birthday":"Sun Jan 01 00:00:00 CST 1995"}
<template-package namespace="kite-student">
<template id="student-detail" data="student" xml-root="student">
<xml-attribute data="id"/>
<property data="name"/>
<property data="classId"/>
<property data="birthday"/>
</template>
</template-package>
xml-root
申明了xml的根节点名称<xml-attribute>
设定了节点的属性
执行结果:
<student id="1">
<name>Peter</name>
<class-id>1</class-id>
<birthday>Sun Jan 01 00:00:00 CST 1995</birthday>
</student>
<!-- /kite/kite-student.xml -->
<!-- 忽略kite-configuration -->
<template-package namespace="kite-student">
<template id="student-detail" data="student">
<property data="id" alias="student_id"/>
<property data="name" alias="student_name"/>
<property data="classId" alias="student_classId"/>
<property data="birthday" alias="student_birthday"/>
</template>
</template-package>
{"student_id":1,"student_name":"Peter","student_classId":1,"student_birthday":"Sun Jan 01 00:00:00 CST 1995"}
该标签可以格式化时间。 把4.1.节代码
<property data="birthday"/>
替换为
<property-date data="birthday" pattern="yyyy-MM-dd"/>
运行结果:
{"id":1,"name":"Peter","classId":1,"birthday":"1995-01-01"}
该标签可以使日期时间类型转化成时间戳形式输出。
dataModel.putData("datetime", LocalDateTime.of(2016, 1, 1, 0, 0, 0));
{"datetime" : 1451577600}
该标签可以把非0数字转换成true,0转换成false
DataModel dataModel = new DataModel();
dataModel.putData("number1", 1);
dataModel.putData("number2", 0);
{"number1" : true, "number2" : false}
该标签可以把值映射成另一个固定值,该标签不仅可以处理枚举类型,字符串或者基本类型都可以处理
<property-enum data="sex">
<enum value="MALE" text="男"/>
<enum value="FEMALE" text="女"/>
</property-enum>
DataModel dataModel = new DataModel();
dataModel.putData("sex", Sex.MALE);
{"sex": "男"}
4.4. 使用null-hidden隐藏值为null的属性
把4.1.节代码
<property data="birthday"/>
替换为
<property data="birthday" null-hidden="true"/>
student实例传入null的birthday值
Student student = new Student(1, "Peter", 1, null);
运行结果:
{"id":1,"name":"Peter","classId":1}
<template id="student-detail" data="student" for-class="test.Student" />
运行结果和4.1.节相同。
使用property-ignore
忽略不需要的属性:
<template id="student-detail" data="student" for-class="test.Student">
<property-ignore name="birthday" />
</template>
运行结果:
{"id":1,"name":"Peter","classId":1}
利用array
标签构造一个数组结构:
<template id="student-list">
<array data="students">
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</array>
</template>
Student peter = new Student(1, "Peter", 1, "1995-01-01");
Student john = new Student(2, "John", 1, "1996-5-20");
Student[] students = {peter, john};
dataModel.putData("students", students);
// isPretty参数设为true,开启json美化
String json = jsonProducer.produce(dataModel, "kite-student", "student-list", true);
{
"students" : [ {
"id" : 1,
"name" : "Peter",
"classId" : 1,
"birthday" : "1995-01-01"
}, {
"id" : 2,
"name" : "John",
"classId" : 1,
"birthday" : "1996-05-20"
} ]
}
或者直接把data设定在<template>
标签上,Kite框架会自动识别data对应的数据是否是数组或List。
<template id="student-list" data="students">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</template>
[ {
"id" : 1,
"name" : "Peter",
"classId" : 1,
"birthday" : "1995-01-01"
}, {
"id" : 2,
"name" : "John",
"classId" : 1,
"birthday" : "1996-05-20"
} ]
<template id="student-list" data="students" xml-root="xml" xml-item="student">
<xml-attribute data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</template>
xml-item
设定数组每项元素的节点名称
<xml>
<students>
<student id="1">
<name>Peter</name>
<class-id>1</class-id>
<birthday>1995-01-01</birthday>
</student>
<student id="2">
<name>John</name>
<class-id>1</class-id>
<birthday>1996-05-20</birthday>
</student>
</students>
</xml>
使用<prototype>
标签可以使用原生的Jackson方式转化实体成json。
<template id="student-detail">
<prototype data="student" />
</template>
@Data
public class Student {
// 编号
private int id;
// 学生名称
@JsonProperty("student_name") // 通过注解@JsonProperty对属性重命名
private String name;
// 班级ID
@JsonIgnore // 通过注解@JsonIgnore忽略该属性
private int classId;
// 出生日期
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd")//通过@JsonFormat格式化日期
private Date birthday;
//构造方法略
}
{
"student" : {
"id" : 1,
"birthday" : "1995-01-01",
"student_name" : "Peter"
}
}
更多注解使用请参考jackson-annotations文档。
com.github.developframework.kite.core.dynamic.KiteConverter
接口可以对表达式选取的属性值进行自定义转换。具体地,是去处理了data
属性找到的值。所以converter
和data
一般同时可用。
@FunctionalInterface
public interface KiteConverter<SOURCE, TARGET> {
/**
* 转化方法
* @param source 源对象
* @return 目标对象
*/
TARGET convert(SOURCE source);
}
- 泛型SOURCE为原数据的类型。
- 泛型TARGET为转化后的类型。
例如将peter的name处理后输出:
<template id="student-detail" data="student">
<property data="id" />
<property data="name" converter="nameConverter"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</template>
dataModel.putData("student", peter);
// 这里采用JDK8 lambda写法
dataModel.putData("nameConverter", (KiteConverter<String, String>) source -> "My name is " + source);
{
"id" : 1,
"name" : "My name is Peter",
"classId" : 1,
"birthday" : "1995-01-01"
}
converter
属性可以填写Expression表达式,还可以填写KiteConverter
的接口实现类全类名。
当转换器用于获取当前data值对象内部属性值时有一种快捷用法:
<template id="student-name">
<property data="student" alias="name" converter="this.name"/>
</template>
<property>
的取值为student,经过converter的转换选取了student对象的name属性作为该节点的值。
在<array>
节点属性map
用于指定对每个元素的映射函数。map
的值可以为Expression
表达式或一个实现com.github.developframework.kite.core.dynamic.MapFunction
接口的完全类名。其中使用表达式方式,其获取的实例必须是com.github.developframework.kite.core.dynamic.MapFunction
的实现类。具体示例:实现功能:对数组的每个元素进行映射处理,处理结果作为生成Json的数据。以下示例在字符串数组的每项元素以value_{item}_{i}
形式输出。
String[] strArray = new String[]{"aaa", "bbb", "ccc"};
dataModel.putData("strArray", strArray);
dataModel.putData("func", ((MapFunction<String, String>) (item, i) -> "value_" + item + "_" + i));
<template id="array-map-demo" >
<array data="strArray" map="func"></array>
</template>
{"str_array":["value_aaa_0","value_bbb_1","value_ccc_2"]}
注意
使用map
会导致<array>
标签的所有子节点失效,因为映射结果将会直接作为json数据。
如果你设置了子节点将会出现以下警告:
The child element invalid, because you use "map" attribute.
使用<object-virtual>
可以虚拟一个对象结构。
利用仅有的属性值,构造一个对象结构:
<template id="student-detail">
<object-virtual alias="student">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</object-virtual>
</template>
dataModel.putData("id", 1);
dataModel.putData("name", "Peter");
dataModel.putData("classId", 1);
dataModel.putData("birthday", "1995-01-01");
{
"student" : {
"id" : 1,
"name" : "Peter",
"classId" : 1,
"birthday" : "1995-01-01"
}
}
使用<include>
标签引用其它<template>
模板,从而可实现模块化设计,避免重复定义视图模板。
<template-package namespace="kite-student">
<template id="student-list" data="students">
<include id="student-detail" />
</template>
<template id="student-detail">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</template>
</template-package>
Kite框架的继承的概念,在<template>
标签可以添加属性extend
指定继承的template和继承的端口。继承的概念可以理解为反向include,调用子template视图,会优先从父template开始构造结构,当遇到匹配端口名的<extend-port>
标签时才会构造子template视图。
注意:
假如单独调用了有<extend-port>
标签的父template视图或者端口没有与之对应的子template实现,则<extend-port>
标签被忽略。
<template-package namespace="kite-student">
<!-- 一个父视图模板 -->
<template id="student-parent">
<object-virtual alias="other">
<property data="otherData" />
</object-virtual>
<!-- 子视图模板的内容将会插入到这个端口位置上 -->
<extend-port port-name="my-port" />
</template>
<!-- 子视图模板 -->
<template id="student-detail" extend="student-parent:my-port">
<!-- 本模板内容将会插入到父视图模板的my-port端口位置上 -->
<object data="student">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</object>
</template>
</template-package>
Student peter = new Student(1, "Peter", 1, "1995-01-01");
dataModel.putData("student", peter);
dataModel.putData("otherData", "I'm other data.");
// 这里调用的子视图模板
String json = jsonProducer.produce(dataModel, "kite-student", "student-detail", true);
{
"other" : {
"otherData" : "I'm other data."
},
"student" : {
"id" : 1,
"name" : "Peter",
"classId" : 1,
"birthday" : "1995-01-01"
}
}
extend
属性的写法为 namespace.templateId:portName 其中namespace可以省略,默认为当前命名空间下。
使用<link>
标签可以在数组间一对一链接对象。 该标签仅能在<array>
下使用。 当<link>
的data属性所指的数组和父<array>
数组个数不相同时将会抛出LinkSizeNotEqualException
。
例子:
假如每个学生实例都有一个账户实例,并且又都一对一对应了一个成绩值。
<template-package namespace="kite-student">
<template id="student-list" data="students">
<property data="id"/>
<property data="name"/>
<property data="classId"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
<!-- 一对一对应accounts数组每项 -->
<link data="#accounts" alias="account">
<!-- 引用另一个命名空间的模板 -->
<include id="account-detail" namespace="kite-account"/>
</link>
<!-- 一对一对应scores数组每项 -->
<link data="#scores" alias="score"/>
</template>
</template-package>
<template-package namespace="kite-account">
<template id="account-detail">
<property data="username"/>
<property data="password"/>
</template>
</template-package>
Account peterAccount = new Account("peter's username", "peter's password");
Account johnAccount = new Account("john's username", "john's password");
Student[] students = {peter, john};
Account[] accounts = {peterAccount, johnAccount};
Integer[] scores = {95, 98};
dataModel.putData("students", students);
dataModel.putData("accounts", accounts);
dataModel.putData("scores", scores);
[ {
"id" : 1,
"name" : "Peter",
"classId" : 1,
"birthday" : "1995-01-01",
"account" : {
"username" : "peter's username",
"password" : "peter's password"
},
"score" : 95
}, {
"id" : 2,
"name" : "John",
"classId" : 1,
"birthday" : "1996-05-20",
"account" : {
"username" : "john's username",
"password" : "john's password"
},
"score" : 98
} ]
假如A数组有2个元素,B数组有3个元素。其中A[0] 需要关联B[0]和B[1], A[1] 需要关联B[2]。这种需求下就可以使用<relevance>
标签,实现在数组间一对多关联。属性rel
指定判定条件,需要实现一个接口:
public interface RelFunction<S, T> {
boolean relevant(S sourceItem, int sourceIndex, T target, int targetIndex);
}
其中泛型S指代A数组类型,T指代B数组类型。
sourceItem是迭代了A数组的每一项,sourceIndex是它的索引。每一项的A元素都会去迭代B数组的每一项target,targetIndex是索引,relevant
方法返回true表示需要关联。
具体看示例,有如下数据结构关系:
// 1班
SchoolClass schoolClass1 = new SchoolClass(1, "1班");
// 2班
SchoolClass schoolClass2 = new SchoolClass(2, "2班");
// 1班的学生
Student peter = new Student(1, "Peter", 1, "1995-01-01");
Student john = new Student(2, "John", 1, "1996-5-20");
// 2班的学生
Student bill = new Student(3, "Bill", 2, "1993-4-16");
Student[] students = {peter, john, bill};
SchoolClass[] schoolClasses = {schoolClass1, schoolClass2};
dataModel.putData("students", students);
dataModel.putData("schoolClasses", schoolClasses);
<template-package namespace="kite-student">
<template id="student-detail">
<property data="name"/>
<property-date data="birthday" pattern="yyyy-MM-dd"/>
</template>
</template-package>
<template-package namespace="kite-class">
<template id="class-list" data="schoolClasses">
<property data="id" />
<property data="className" />
<!-- 关联学生列表 -->
<relevance data="#students" rel="rel">
<include id="student-detail" namespace="kite-student" />
</relevance>
</template>
</template-package>
实现RelFunction
dataModel.putData("rel", (RelFunction<SchoolClass, Student>) (sourceItem, sourceIndex, target, targetIndex) -> sourceItem.getId() == target.getClassId());
判定条件为当SchoolClass(sourceItem)的id与Student(target)的classId相等时,允许关联。
[ {
"id" : 1,
"className" : "1班",
"students" : [ {
"name" : "Peter",
"birthday" : "1995-01-01"
}, {
"name" : "John",
"birthday" : "1996-05-20"
} ]
}, {
"id" : 2,
"className" : "2班",
"students" : [ {
"name" : "Bill",
"birthday" : "1993-04-16"
} ]
} ]
可以使用<if>
<else>
标签进行模块内容的取舍。<else>
标签可以不写,但必须紧跟<if>
后出现。
<if>
标签的condition
属性内容为接口com.github.developframework.kite.core.dynamic.KiteCondition
的实现类。
@FunctionalInterface
public interface Condition<T> {
/**
* 判断条件
* @param dataModel 数据模型
* @param currentValue 当前值
* @return 判断结果
*/
boolean verify(DataModel dataModel, T currentValue);
}
最简范例:
<template id="first-view">
<if condition="myCondition">
<property data="sayHello"/>
</if>
<else>
<property data="sayBye"/>
</else>
</template>
dataModel.putData("sayHello", "Hello");
dataModel.putData("sayBye", "Bye");
dataModel.putData("myCondition", (Condition) (dm, expression) -> true);
// 或直接使用boolean
// dataModel.putData("myCondition", true);
{"sayHello" : "Hello"}
当myCondition 接口返回false时
{"sayBye" : "Bye"}
可以使用<switch>
<case>
<default>
标签进行模块内容的选择。<case>
标签可以出现多个,<default>
只能出现一次,并且只能是<switch>
标签的最后一个子节点。
<case>
标签的test
属性内容为接口com.github.developframework.kite.core.dynamic.CaseTestFunction
的实现类。
@FunctionalInterface
public interface CaseTestFunction<T> {
/**
* 测试选择
*
* @param data switch传入的值
* @return 是否选中该分支
*/
boolean test(T data);
}
最简范例:
<template id="first-view">
<switch check-data="switchData">
<case test="testCase1">
<property data="sayHello"/>
</case>
<case test="testCase2">
<property data="sayThanks"/>
</case>
<default>
<property data="sayBye"/>
</default>
</switch>
</template>
dataModel.putData("switchData", 1);
dataModel.putData("sayHello", "Hello");
dataModel.putData("sayThanks", "Thanks");
dataModel.putData("sayBye", "Bye");
dataModel.putData("testCase1", (CaseTestFunction<Integer>) value -> value == 1);
dataModel.putData("testCase2", (CaseTestFunction<Integer>) value -> value == 2);
当switchData
等于1时输出
{"sayHello" : "Hello"}
当switchData
等于2时输出
{"sayThanks" : "Thanks"}
当switchData
等于3时输出
{"sayBye" : "Bye"}
json节点和xml节点名称的命名策略扩展,继承接口com.github.developframework.kite.core.strategy.KitePropertyNamingStrategy
public interface KitePropertyNamingStrategy {
/**
属性展示名称
*/
String propertyShowName(KiteConfiguration configuration, String expressionString);
}
通过接口方法得到生成的名称。
Kite内置接口实现:
- JACKSON JacksonKitePropertyNamingStrategy 用Jackson配置的策略命名
- MIDDLE_LINE MiddleLineKitePropertyNamingStrategy 中划线命名策略,AbCd => ab-cd
- UNDERLINE UnderlineXmlKitePropertyNamingStrategy 下划线xml命名策略,AbCd => ab_cd
- LOWDER_CASE LowerCaseKitePropertyNamingStrategy 全小写命名策略, AbCd => abcd
- ORIGINAL OriginalKitePropertyNamingStrategy 什么都不做,使用原名
- DEFAULT json默认使用JACKSON策略,xml默认使用MIDDLE_LINE策略
可以在容器节点上配置children-naming-strategy
属性强制使用某个策略
<template id="" data="" children-naming-strategy="MIDDLE_LINE">
<property data=""/>
<object data="" children-naming-strategy="UNDERLINE">
<property data=""/>
</object>
</template>
Kite框架使用slf4j-api日志接口,提供内部日志打印功能。可以使用log4j或者logback打印日志。 以下示例使用logback
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>kite-log</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.github.developframework.kite" level="INFO" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
项目启动日志:
09:29:07.753 DEBUG - Kite framework loaded the configuration source "/kite/kite-demo.xml".