工作上有在中间件中接收业务系统对象, 并根据当前配置转换成目标系统对象的 JSON 字符串的需求. 通过 FastJSON + 组合注解的方式即可实现.
基础设施
ApiDataAdapter
ApiDataAdapter 是一个组合注解, 它有一个成员属性, 类型是注解 ApiDataAdapter.Finance 的数组.
ApiDataAdapter.Finance 是被嵌套的注解, 它的意思是在当前类型 (type
) 和版本 (version
) 下, 被标注的字段在 JSON 中的字段名由 fieldName
指定.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiDataAdapter {
/**
* Description: 财务接口适配
*
* @return {@link Finance[]}
* @author LiKe
* @date 2019-11-03 13:09:32
*/
Finance[] value();
/**
* Description: 标注 财务接口类型、字段名的 注解
*
* @author LiKe
* @date 2019-11-03 12:53:29
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Finance {
/**
* Description: 接口类型
*
* @return {@link FinanceType}
* @author LiKe
* @date 2019-11-03 13:04:28
*/
FinanceType type();
/**
* Description: 接口版本
*
* @return {@link FinanceVersion}
* @author LiKe
* @date 2019-11-03 13:08:00
*/
FinanceVersion version();
/**
* Description: 在当前接口类型和接口版本下, 对应的字段名
*
* @return java.lang.String
* @author LiKe
* @date 2019-11-03 13:08:39
*/
String fieldName();
}
}
ApiCache
这是一个缓存类, 用于缓存前面提到的 type
和 version
.
public class ApiCache {
/**
* Description: 获取配置缓存
*
* @param key {@link Key} 缓存的 Key
* @return java.lang.String
* @throws RuntimeException 当不存在指定 Key 的缓存时
* @author LiKe
* @date 2019-11-02 16:14:29
*/
public static String get(ApiCache.Key key) {
final String cacheValue = getInstance().get(key.definition);
if (StringUtils.isBlank(cacheValue)) {
throw new RuntimeException(String.format("缓存中不存在 %s", key.definition));
}
return cacheValue;
}
/**
* Description: 核心配置缓存 Key 定义
*
* @author LiKe
* @date 2019-11-02 15:33:09
*/
public enum Key {
/**
* 财务接口类型
*/
FINANCE_TYPE("[FINANCE_TYPE]"),
/**
* 财务接口版本
*/
FINANCE_VERSION("[FINANCE_VERSION]");
private final String definition;
Key(String definition) {
this.definition = definition;
}
}
/**
* Description: 构建器
*
* @author LiKe
* @date 2019-11-02 15:23:31
*/
public static class Builder {
/**
* Description: 初始化构建器
*
* @return {@link Builder}
* @author LiKe
* @date 2019-11-02 15:31:30
*/
public static Builder of() {
return new Builder();
}
/**
* Description: 缓存财务接口类型
*
* @param financeType 财务接口类型
* @return {@link Builder}
* @author LiKe
* @date 2019-11-02 15:49:06
*/
public Builder financeType(FinanceType financeType) {
ApiCache.getInstance().put(Key.FINANCE_TYPE.definition, financeType.name());
return this;
}
/**
* Description: 缓存财务接口版本
*
* @param financeVersion 财务接口版本
* @return {@link Builder}
* @author LiKe
* @date 2019-11-02 15:49:42
*/
public Builder financeVersion(FinanceVersion financeVersion) {
ApiCache.getInstance().put(Key.FINANCE_VERSION.definition, financeVersion.getVersion());
return this;
}
}
/**
* Description: 单例 Holder
*
* @author LiKe
* @date 2019-11-02 11:39:41
*/
private static class SingletonHolder {
private static final ConcurrentHashMap<String, String> map = initialize();
private static ConcurrentHashMap<String, String> initialize() {
if (Objects.isNull(map))
return new ConcurrentHashMap<>();
return map;
}
}
/**
* Description: 获取单例缓存实例
*
* @return java.util.Map
* @author LiKe
* @date 2019-11-02 15:40:06
*/
private static Map<String, String> getInstance() {
return SingletonHolder.map;
}
}
复杂对象
新建复杂对象用于模拟测试, 一个 Business 对象, 包含 3 个成员属性: RequestBusinessData
, List<RequestOutlayData>
, List<RequestListData>
, 定义如下:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class Business extends DeserializeDescriptor {
/**
* 业务数据
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestBusinessData#TC-v20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestBusinessData#TY-v20190401")
}
)
private RequestBusinessData businessData;
/**
* 经费数据
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestOutlayData#TC-v20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestOutlayData#TY-v20190401")
}
)
@Singular(value = "outlayData")
private List<RequestOutlayData> outlayData;
/**
* 资产数据
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestListData#TC-v20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestListData#TY-v20190401")
}
)
@Singular(value = "requestListData")
private List<RequestListData> requestListData;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class RequestBusinessData extends DeserializeDescriptor {
/**
* 业务号
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestBusinessData#bpmNo-TC-20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestBusinessData#bpmNo-TY-20190528")
}
)
private String bpmNo;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class RequestListData extends DeserializeDescriptor {
/**
* 业务号
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestListData#bpmNo-TC-v20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestListData#bpmNo-TY-v20190401")
}
)
private String bpmNo;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class RequestOutlayData extends DeserializeDescriptor {
/**
* 业务号
*/
@ApiDataAdapter(
{
@ApiDataAdapter.Finance(type = FinanceType.TC, version = FinanceVersion.v20180907_TC, fieldName = "RequestOutlayData#bpmNo-TC-v20180907"),
@ApiDataAdapter.Finance(type = FinanceType.TY, version = FinanceVersion.v20190401_TY, fieldName = "RequestOutlayData#bpmNo-TY-v20190401")
}
)
private String bpmNo;
}
(这里用到了 Lombok 简化代码)
序列化
新建一个类, 命名为 Wrapper. 提供一个序列化方法, 利用 FastJson 的 SerializeFilter, 其中 NameFilter 根据当前缓存中的 type
和 version
将对象中的属性名替换成注解上由匹配 type
和 version
指定的 fieldName
; PropertyFilter 用于决定是否跳过某个属性的序列化 :
public static <GenericData> String to(GenericData clientData) {
final String financeType = ApiCache.get(ApiCache.Key.FINANCE_TYPE), financeVersion = ApiCache.get(ApiCache.Key.FINANCE_VERSION);
return JSON.toJSONString(
clientData,
new SerializeFilter[]{
(NameFilter) (object, name, value) -> {
try {
Field field = object.getClass().getDeclaredField(name);
for (ApiDataAdapter.Finance finance : field.getAnnotation(ApiDataAdapter.class).value()) {
if (financeType.equals(finance.type().name()) && (financeVersion + "_" + financeType).equals(finance.version().name())) {
return finance.fieldName();
}
}
} catch (NoSuchFieldException ignored) {
}
return name;
},
(PropertyFilter) (object, name, value) -> {
try {
// 没有加注解,不做映射
if (Objects.isNull(object.getClass().getDeclaredField(name).getAnnotation(ApiDataAdapter.class)))
return false;
} catch (NoSuchFieldException ignored) {
}
return true;
}
}
);
}
反序列化
反序列化稍微麻烦点, 需要借助从 FastJSON-1.2.35 版本才提供的支持自定义反序列化的 API: PropertyProcessable.
进行一波再抽象, 我们自己实现其方法, 然后让每个对象都继承自己的抽象类,
public abstract class DeserializeDescriptor implements Serializable, PropertyProcessable {
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
@Override
public Type getType(String name) {
return PropertyProcessableHelper.getTypeHelper(name, this.getClass());
}
@Override
public void apply(String name, Object value) {
Field field = PropertyProcessableHelper.applyHelper(name, this.getClass());
if (Objects.nonNull(field)) {
field.setAccessible(true);
try {
field.set(this, value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* Description: {@link PropertyProcessable} 实现方法抽象
*
* @author LiKe
* @date 2019-11-12 09:41:14
*/
private static class PropertyProcessableHelper {
/**
* Description: 根据key和当前财务类型版本信息找出当前属性TYPE
*
* @param fieldName 当前属性名
* @param targetClazz 目标对象的 CLASS 对象
* @return java.lang.reflect.Type
* @author LiKe
* @date 2019/11/10 17:44
*/
static Type getTypeHelper(String fieldName, Class<?> targetClazz) {
for (Field field : targetClazz.getDeclaredFields()) {
ApiDataAdapter annotation = field.getAnnotation(ApiDataAdapter.class);
if (Objects.nonNull(annotation)) {
ApiDataAdapter.Finance[] financeAnnotatedCandidates = annotation.value();
for (ApiDataAdapter.Finance finance : financeAnnotatedCandidates) {
if (
ApiCache.get(ApiCache.Key.FINANCE_TYPE).equals(finance.type().name()) &&
(ApiCache.get(ApiCache.Key.FINANCE_VERSION) + "_" + ApiCache.get(ApiCache.Key.FINANCE_TYPE)).equals(finance.version().name()) &&
fieldName.equals(finance.fieldName())
) {
return field.getGenericType();
}
}
}
}
return null;
}
/**
* Description: 根据key和当前财务类型版本信息找出当前属性Field对象
*
* @param fieldName 当前属性名
* @param targetClazz 目标对象的 CLASS 对象
* @return java.lang.reflect.Field
* @author LiKe
* @date 2019/11/10 17:46
*/
static Field applyHelper(String fieldName, Class<?> targetClazz) {
for (Field field : targetClazz.getDeclaredFields()) {
ApiDataAdapter annotation = field.getAnnotation(ApiDataAdapter.class);
if (Objects.nonNull(annotation)) {
ApiDataAdapter.Finance[] financeAnnotatedCandidates = annotation.value();
for (ApiDataAdapter.Finance finance : financeAnnotatedCandidates) {
if (
ApiCache.get(ApiCache.Key.FINANCE_TYPE).equals(finance.type().name()) &&
(ApiCache.get(ApiCache.Key.FINANCE_VERSION) + "_" + ApiCache.get(ApiCache.Key.FINANCE_TYPE)).equals(finance.version().name()) &&
fieldName.equals(finance.fieldName())
) {
return field;
}
}
}
}
return null;
}
}
}
最后在 Wrapper 中新增反序列化的方法:
/**
* Description: 把服务端返回 JSON 数据解析成对客户端友好的 JAVA 对象<br>
* NecessaryCondition: 如果目标类实现了 {@link cn.caplike.demo.repository.design.pattern.combined.annotation.definition.DeserializeDescriptor}
* 并且目标类的 field 标注了 {@link ApiDataAdapter}
*
* @param jsonStr 服务端返回 JSON 数据
* @param clazz 客户端友好的 JAVA 对象的 CLASS 对象,
* <strong>需要实现 {@link cn.caplike.demo.repository.design.pattern.combined.annotation.definition.DeserializeDescriptor}
* 超类</strong>
* @return GenericClientObject 客户端友好的 JAVA 对象
* @author LiKe
* @date 2019-11-06 09:32:22
*/
@SuppressWarnings("unchecked")
public static <GenericClientObject> List<GenericClientObject> parse(String jsonStr, Class<GenericClientObject> clazz) {
if (String.class.equals(clazz))
return (List<GenericClientObject>) Collections.singletonList(jsonStr);
Object any = JSON.parse(jsonStr);
if (any instanceof JSONObject)
return Collections.singletonList(JSON.parseObject(jsonStr, clazz));
if (any instanceof JSONArray)
return JSON.parseArray(jsonStr, clazz);
throw new RuntimeException("未能识别的类型: " + any.getClass().getCanonicalName());
}
enable_autotype
另外, 在常规反序列化操作时, 如果遇到报错 autoType is not support. 可以通过官方说明来解决.
2017年3月15日,fastjson官方发布安全升级公告,该公告介绍fastjson在1.2.24及之前的版本存在代码执行漏洞,当恶意攻击者提交一个精心构造的序列化数据到服务端时,由于fastjson在反序列化时存在漏洞,可导致远程任意代码执行。
自1.2.25及之后的版本,禁用了部分autotype的功能,也就是”@type”这种指定类型的功能会被限制在一定范围内使用。
而由于反序列化对象时,需要检查是否开启了autotype。所以如果反序列化检查时,autotype没有开启,就会报错。
测试
新建测试类, 并执行测试:
// ~ 缓存
ApiCache.Builder.of().financeType(FinanceType.TC).financeVersion(FinanceVersion.v20180907_TC);
// ~ 初始化对象
final RequestBusinessData businessData = RequestBusinessData.builder().bpmNo("bpmNo").build();
final String firstId = UUID.randomUUID().toString();
final String secondId = UUID.randomUUID().toString();
final String thirdId = UUID.randomUUID().toString();
final List<RequestOutlayData> requestOutlayDataList = Stream.of(
RequestOutlayData.builder().bpmNo(firstId).build(),
RequestOutlayData.builder().bpmNo(secondId).build(),
RequestOutlayData.builder().bpmNo(thirdId).build()
).collect(Collectors.toList());
final List<RequestListData> requestListDataList = Stream.of(
RequestListData.builder().bpmNo(firstId).build(),
RequestListData.builder().bpmNo(secondId).build(),
RequestListData.builder().bpmNo(thirdId).build()
).collect(Collectors.toList());
final Business business = Business.builder().businessData(businessData).outlayData(requestOutlayDataList).requestListData(requestListDataList).build();
final String json = Wrapper.to(business);
System.out.println("json: " + json);
final List<Business> parse = Wrapper.parse(json, Business.class);
System.out.println("data: " + parse.get(0));
可以看到控制台输出:
json: {"RequestBusinessData#TC-v20180907":{"RequestBusinessData#bpmNo-TC-20180907":"bpmNo"},"RequestOutlayData#TC-v20180907":[{"RequestOutlayData#bpmNo-TC-v20180907":"8b11af14-294e-42b9-923d-ec09513663fa"},{"RequestOutlayData#bpmNo-TC-v20180907":"7ccd88d3-adb2-4a63-b9bc-92b0a06310e3"},{"RequestOutlayData#bpmNo-TC-v20180907":"b683f7b5-4f28-47e5-b0fb-5f65008f59a9"}],"RequestListData#TC-v20180907":[{"RequestListData#bpmNo-TC-v20180907":"8b11af14-294e-42b9-923d-ec09513663fa"},{"RequestListData#bpmNo-TC-v20180907":"7ccd88d3-adb2-4a63-b9bc-92b0a06310e3"},{"RequestListData#bpmNo-TC-v20180907":"b683f7b5-4f28-47e5-b0fb-5f65008f59a9"}]}
data: Business(businessData=RequestBusinessData(bpmNo=bpmNo), outlayData=[RequestOutlayData(bpmNo=8b11af14-294e-42b9-923d-ec09513663fa), RequestOutlayData(bpmNo=7ccd88d3-adb2-4a63-b9bc-92b0a06310e3), RequestOutlayData(bpmNo=b683f7b5-4f28-47e5-b0fb-5f65008f59a9)], requestListData=[RequestListData(bpmNo=8b11af14-294e-42b9-923d-ec09513663fa), RequestListData(bpmNo=7ccd88d3-adb2-4a63-b9bc-92b0a06310e3), RequestListData(bpmNo=b683f7b5-4f28-47e5-b0fb-5f65008f59a9)])
将缓存属性改为, 可以看到
json: {"RequestBusinessData#TY-v20190401":{"RequestBusinessData#bpmNo-TY-20190528":"bpmNo"},"RequestOutlayData#TY-v20190401":[{"RequestOutlayData#bpmNo-TY-v20190401":"1646e278-9423-4a9d-b9d6-4df604c9569e"},{"RequestOutlayData#bpmNo-TY-v20190401":"4e3e4619-e0f1-42eb-87b0-f52ee58cc551"},{"RequestOutlayData#bpmNo-TY-v20190401":"bb62e710-0b16-46bd-a236-e7ab6dddbf60"}],"RequestListData#TY-v20190401":[{"RequestListData#bpmNo-TY-v20190401":"1646e278-9423-4a9d-b9d6-4df604c9569e"},{"RequestListData#bpmNo-TY-v20190401":"4e3e4619-e0f1-42eb-87b0-f52ee58cc551"},{"RequestListData#bpmNo-TY-v20190401":"bb62e710-0b16-46bd-a236-e7ab6dddbf60"}]}
data: Business(businessData=RequestBusinessData(bpmNo=bpmNo), outlayData=[RequestOutlayData(bpmNo=1646e278-9423-4a9d-b9d6-4df604c9569e), RequestOutlayData(bpmNo=4e3e4619-e0f1-42eb-87b0-f52ee58cc551), RequestOutlayData(bpmNo=bb62e710-0b16-46bd-a236-e7ab6dddbf60)], requestListData=[RequestListData(bpmNo=1646e278-9423-4a9d-b9d6-4df604c9569e), RequestListData(bpmNo=4e3e4619-e0f1-42eb-87b0-f52ee58cc551), RequestListData(bpmNo=bb62e710-0b16-46bd-a236-e7ab6dddbf60)])
这样就实现了: 根据配置属性, 序列化相同的对象成对应的 JSON, 反序列化亦然.
- END -