java 反射 自定义类型_java通过自定义注解+反射实现规则引擎

介绍

最近遇到一个钉钉微应用的项目,前端自实现,后端数据操作需要对接其他系统。

总结

总结一下后端需要做的事情。

对前端参数进行校验。比如数值范围,字符串等类型,某些值要小于从其他系统查到的余额等场景。

请求转发。前端传递json类型,要传到某些其他系统中,类型不确定,有的是xml,有的是json类型。

请求响应。收到响应请求时,需要转成json类型。

收到的结构数据中,有字符串是json的类型,需要转成json类型

字段名称需要映射,比如jobNo对应其他系统的T_ABC

以上就是新系统需要做的事情,涉及的接口近百个左右。如果不出意外的场景。正常写代码的话。

需要各种json解析,一对一的解析,拼装请求,http代码,解析响应,返回给前端。看了下demo,一个接口的代码量在100行以上完全不是问题。

大量重复的代码堆积。开启了优化重构之路。

寻找共性

通过对上述接口的分解,不难看出其共性:字段映射,较强大的规则校验,,json转xml。

字段映射

通过配置化json即可。

json和xml的互转

dom4j解析xml实现

规则化json

{“data”: “{\”name\”: \”abc\”}”} -> {“data”:{“name”: “abc”}}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38/**

* 将不规则的json数据解析成序列化json

* @param jsonObject

*/

public void convert2JSONObject(JSONObject jsonObject){

if(jsonObject != null && jsonObject.size() > 0) {

for(String key: jsonObject.keySet()) {

Object object = jsonObject.get(key);

if(object instanceof JSONObject) {

convert2JSONObject(jsonObject.getJSONObject(key));

}else if(object instanceof JSONArray) {

JSONArray array = jsonObject.getJSONArray(key);

for(int i=0 ;i< array.size(); i++) {

convert2JSONObject(array.getJSONObject(i));

}

}else if(object instanceof String) {

String line = object.toString();

if(line.contains("[{")) {

try{

jsonObject.put(key, JSON.parseArray(line));

}catch (Exception e) {

//fixed logger

logger.error("line format to json array fail, line: {}", line);

throw new InvalidArgumentException(String.format("line format to json array fail, line: %s", line));

}

}else if(line.contains("{")) {

try{

jsonObject.put(key, JSON.parseObject(line));

}catch (Exception e) {

//fixed logger

logger.error("line format to json object fail, line: {}", line);

throw new InvalidArgumentException(String.format("line format to json object fail, line: %s", line));

}

}

}

}

}

}

规则解析引擎

定义一个规则类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19/***

* 用于流程归档规则校验

*/

@JsonTop(path="data.I_INPUT", type= Constant.OpCode.STRING)

public class ProcessArchiveRule extends Rule{

@UrlCompare(url="http://localhost/name/query?id=", args="id" resp="a.b.c")

@NotNull

@NumberCompare(eq = "1", type=Constant.OpCode.INT)

private String status;

@NumberCompare(eq = "10001", type=Constant.OpCode.INT)

private String pernr;

@Extend

public void extend(String jsonStr){

System.out.println(String.format("jsonStr:",jsonStr));

}

}

配置文件

1checkConfigs=[{"processArchive":"ProcessArchiveRule.class"},{"processApprove":"ProcessApproveRule.class"}]

解释

@JsonTop path代表要解析传参的路径, type代表我要转义的类型string/object/array

下面每个字段都会选择去递归其path下的key进行对比逐个字段上注解的校验。

@NumberCompare 代表对其数值的校验。

@UrlCompare 代表对比的该值需要从其他url中获取,可申明json/xml类型。

@extend 代表是否需要自定义扩展check。比如有注解做不到的场景时,支持扩展。

如此就能完成自动化校验。

规则校验部分代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134/***

* 匹配规则bean类

* @param body

*/

public void checkRule(String body){

JSONObject jsonObject = JSONObject.parseObject(body);

String method = jsonObject.getString(Constant.OpCode.METHOD);

StringBuilder sb = new StringBuilder(Constant.OpCode.BEAN_PACKAGE);

sb.append(method.substring(0,1).toUpperCase()).append(method.substring(1,method.length())).append(Constant.OpCode.RULE);

String bean = sb.toString();

//获得类级别的注解对象

try {

List> list = ruleClazzList;

for (int i=0; i

Map beanMap = list.get(i);

for (String key : beanMap.keySet()){

if(key.equals(method)){

Class clazz =Class.forName(bean);

JsonTop jsonTop = (JsonTop) clazz.getAnnotation(JsonTop.class);

String[] tops = jsonTop.path().split("\\.");

loopTopJson(clazz,jsonObject,tops, 0, Constant.OpCode.STRING);

}

}

}

} catch (ClassNotFoundException e) {

logger.error("{} class is ClassCastException", bean);

throw new InvalidArgumentException(String.format("%s class is ClassCastException", bean));

}

}

/**

* 递归获取到rule类注解的path

* @param clazz

* @param jsonObject

* @param tops

* @param index

* @param type

*/

private void loopTopJson(Class clazz,JSONObject jsonObject, String[] tops, int index, String type){

if(index >= tops.length - 1) {

if(Constant.OpCode.STRING.equals(type)) {

logger.info(jsonObject.getString(tops[index]));

validateRule(clazz, jsonObject.getString(tops[index]));

}else if(Constant.OpCode.OBJECT.equals(type)) {

logger.info(jsonObject.getString(tops[index]));

validateRule(clazz, jsonObject.getString(tops[index]));

}

}else{

if (jsonObject.containsKey(tops[index])) {

Object object = jsonObject.get(tops[index]);

if (object instanceof JSONObject) {

loopTopJson(clazz,jsonObject.getJSONObject(tops[index]), tops, index++, type);

} else if (object instanceof JSONArray) {

JSONArray array = jsonObject.getJSONArray(tops[index]);

if(array.size() > 0) {

for(int i=0; i

loopTopJson(clazz,array.getJSONObject(i), tops, index+1, type);

}

}

} else {

Object obj = jsonObject.get(tops[index]);

}

}

}

}

/**

* 验证其json类型

* @param clazz 对应类名

* @param data json数据

*/

private void validateRule(Class clazz, String data){

Object object = JSON.parse(data);

if(object instanceof JSONObject) {

JSONObject jsonObject = JSON.parseObject(data);

validateRule(clazz, jsonObject);

}else if(object instanceof JSONArray) {

JSONArray jsonArray = JSON.parseArray(data);

for(int i=0 ;i

validateRule(clazz, jsonArray.getJSONObject(i));

}

}

}

/***

* 将匹配到的json数据与rule上的注解进行逐一check

* @param clazz 对应类名

* @param data json数据

*/

public void validateRule(Class clazz, JSONObject data){

loopField: for(Field field: clazz.getDeclaredFields()) {

if(field.isAnnotationPresent(NumberCompare.class)) {

if(! data.containsKey(field.getName())) {

logger.info("field:{} not find from data: {}", field.getName(), data.keySet());

continue loopField;

}

NumberCompare number = field.getAnnotation(NumberCompare.class);

String checkEq = number.eq();

String dataField = data.getString(field.getName());

if(number.type().equals(Constant.OpCode.INT)) {

if(Integer.parseInt(checkEq) != Integer.parseInt(dataField)) {

throw new InvalidArgumentException(String.format("%s value is %s, need equals %s", field.getName(), dataField, checkEq));

}

}else if(number.type().equals(Constant.OpCode.DOUBLE)) {

if(Double.parseDouble(checkEq) != Double.parseDouble(dataField)) {

throw new InvalidArgumentException(String.format("%s value is %s, need equals %s", field.getName(), dataField, checkEq));

}

}

}

if (field.isAnnotationPresent(NotNull.class)) {

if(! data.containsKey(field.getName())) {

logger.info("field:{} not find from data: {}", field.getName(), data.keySet());

continue loopField;

}

checkNotNull(data.getString(field.getName()), field.getName());

}

if (field.isAnnotationPresent(UrlCompare.class)) {

if(! data.containsKey(field.getName())) {

logger.info("field:{} not find from data: {}", field.getName(), data.keySet());

continue loopField;

}

UrlCompare url = field.getAnnotation(UrlCompare.class);

//append url annotations

}

}

}

剩下的只是针对不同的接口写不同的Rule类,声明要check的字段,需要check的类型注解即可。实现代码的优化,简化。代码量指数级的下降。当然只针对这一场景。

为其他场景提供一个思路。代码比较简单,coding 3小时基本差不多能fix。

主要涉及到的技术点:自定义注解,递归,反射

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值