通过 FastJSON + 组合注解实现数据对象"一对多"的序列化和反序列化


工作上有在中间件中接收业务系统对象, 并根据当前配置转换成目标系统对象的 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

这是一个缓存类, 用于缓存前面提到的 typeversion.

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 根据当前缓存中的 typeversion 将对象中的属性名替换成注解上由匹配 typeversion 指定的 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 -

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值