本文仅用作自己在学习路上遇到问题、解决问题方面的记录。如被提供给同样在此方面遇到问题的同学参考,不胜荣幸。若文中有出现纰漏不够严谨之处,望海涵。意见之处,不吝赐教。
业务场景
在对接Bpm宏天工作流时,需要传递指定格式如下
1
2
3
4{
"businessKey": "string",
"data": "string"
}其中businessKey是用来映射流程模型的,而data里存放的则是JSON格式的表单数据。对于表单数据仍然需要拥有“Key”来映射表单。
不同的流程需要映射不同的Key,不同的表单也需要映射不同的Key。因此在传递data数据进行对象的序列化时需要动态变更不同对象的序列化名称。常见的做法是封装成DTO进行集成Set,但是缺点在于存在不同类型对象多个流程情况下代码不够解耦。并且当属性名变更时更改代码显得效率不高。因而想要实现注解方式通过更改配置或公共常量来完成代码的解耦和业务场景的实现
设计思路
泛型加注解
泛型的目的是为了容纳不同类别的对象
注解的目的是在对象序列化时动态调整对象的名称
经过相关学习了解到,SerializeFilter是通过编程扩展的方式定制fastjso序列化。fastjson支持6种SerializeFilter,用于不同场景的定制序列化。NameFilter是其中之一。
根据源码得知,NameFilter下有一个process接口,该接口的目的是序列化时返回指定的名称。结合本次业务场景重写process方法即可。
定义注解前置学习
- 首先在定义注解前要了解,两个注解,@Rentention和@Target。
- @Rentention主要是用来修饰注解的生命周期及作用范围,一共有RententionPolicy.SOURCE、RententionPolicy.CLASS、RententionPolicy.RUNTIME三种类型。默认是CLASS类型的,生命周期从小到大排序为SOURCE/CLASS/RUNTIME。
- 三者区别在于SOURCE只保留在源文件中,当.java编译成.class时注解就会消失。CLASS则会保留到变成.class的时候,但.class被加载成字节码的时候注解会消失,而RUNTIME存在的时间最久,在运行时仍然是生效的。
- 至于怎么选择三种类型以实际开发为准,像SOURCE这种存在时间短的一般二开应用场景不多,而CLASS这种一般用于编译时对代码规范的检查例如@Override等,而我选择RUNTIME是因为业务场景都是在系统运行时需要使用的。而@Target简而言之就是对注解使用范围的说明,包下、方法中、参数上等等。
实现思路及过程
本次自定义注解实现时,定义了两个接口和一个实现类,最外层的注解是一个注解接口,MyParamName,MyParamName中存在两个属性,一个是默认序列化的名称,另一个是数组类型的动态取到的名称。
其中数组类型是第二个注解,NameEle。NameEle中存在id和value两个参数。它的作用是在面对多个参数时,动态选择需要映射成的value,有点key-value的意思。当然这两个注解都属于可以运行时生效的注解。
最后是定义实现类实现fastjson的nameFilter接口,重写process方法。
重写的逻辑大致是通过反射获取注解名为MyParamName的注解,然后遍历MyParamName中的两个参数,如果默认序列化名称不为空且第二个注解参数NameEle为空则返回默认名称,此种情况用于判断单个参数的取值。如果NameEle非空,则通过遍历数组NameEle利用NameFilter定义的id属性和NameEle中的id进行匹配,取出指定的value值即可。
实现效果
泛型DTO
动态序列化
效果
Tips:
- 除了nameFilter外,fastjson还提供了PropertyPreFilter(根据Key判断是否序列化) 、PropertyFilter(根据属性名Key和属性值Value来判断是否序列化)、ValueFilter (修改Value)、BeforeFilter (序列化时在最前添加内容)、AfterFilter(序列化时在最后添加内容)等接口。