自定義注解Dict姊氓,解決數(shù)據(jù)字典批量翻譯繁瑣的問題

概述

由于業(yè)務(wù)的需要丐怯,錄入很多信息,而這些信息中絕大多數(shù)都是字典code翔横,所以就會有很多數(shù)據(jù)字典的定義读跷。
而前臺數(shù)據(jù)的獲取基本上都是下拉列表的選擇。在保存或編輯的時候禾唁,返回到前端只要字典code就可以舔亭,但是在查看的時候些膨,需要將字典翻譯同時返回用于展示。

基于這樣的前提下钦铺,受Hibernate Validator校驗方式的啟示,通過自定義注解的方式來實現(xiàn)肢预。
這樣做有一個非常大的優(yōu)勢:可拓展性強矛洞,以后新增字典項或字典類型直接加入字典倉庫,然后在相關(guān)字段上聲明就可以了烫映。

實現(xiàn)的功能

由于公司項目的特殊性沼本,存在老的字典碼和新字典碼映射轉(zhuǎn)換;所以除了字典code的翻譯锭沟,還有字典code的轉(zhuǎn)換抽兆。
除了一對一的字典code的翻譯,還有一種是上下級的翻譯族淮,比如省市區(qū)

  1. 一般字典code的翻譯
  2. 上下級字典code的翻譯
  3. 字典code的轉(zhuǎn)換(主要是新老系統(tǒng)的兼容對接)

具體實現(xiàn)

話不多說辫红,來看示例:

  1. 首先在DTO中添加自定義的注解
@Data
public class PersonDTO {
    /** 證件類型(字典) */
    private String custCertificateType;
    /** 學歷(字典) */
    private String custEducation;
    /** 學位(字典) */
    private String custDegree;
    /** 單位所在省份(字典) */
    private String companyProvince;
    /** 單位所在城市(字典) */
    private String companyCity;
    /** 單位所在區(qū)/縣(字典) */
    private String companyDistrict;

    /** 證件類型(名稱) */
    @Dict(DICT_CERTIFICATE_TYPE)
    private String custCertificateTypeName;
    /** 學歷(名稱) */
    @Dict(DICT_EDUCATION)
    private String custEducationName;
    /** 學位(名稱) */
    @Dict(DICT_DEGREE)
    private String custDegreeName;

    /** 單位所在省份(名稱) */
    @Dict(value = DICT_AR_PROVINCE)
    private String companyProvinceName;
    /** 單位所在城市(名稱) */
    @Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyProvince")
    private String companyCityName;
    /** 單位所在區(qū)/縣(名稱) */
    @Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyCity")
    private String companyDistrictName;
}

自定義注解Dict的定義如下:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {

    /** 數(shù)據(jù)字典code類型; 如果查找二級編碼祝辣,則該值存儲二級編碼類型前綴贴妻,比如AR表示區(qū)域 */
    String value() default "";

    /** 存儲編號的字段名稱 如果是添加了Name后綴,則可省略*/
    String codeName() default "";

    /** 是否是子元素蝙斜,如果是true名惩,則 parentField不得為空*/
    boolean isChild() default false;

    /** 子元素的上級code字段名 */
    String parentField() default "";

    /** 當轉(zhuǎn)換后的值為null的時候,是否進行賦值孕荠;默認為false娩鹉,表示不覆蓋 */
    boolean whetherSetIfNull() default false;

    /** 是否是轉(zhuǎn)換編碼;也就是說默認不轉(zhuǎn)換code稚伍,而是翻譯成name */
    boolean whetherConvertCode() default false;
}
  1. 其次是在接口輸出list結(jié)果前調(diào)用service方法解析@Dict注解

使用注解的方式自定義解析弯予,比較靈活。哪里需要使用槐瑞,哪里調(diào)用方法解析就可以熙涤。

@Autowired private DictService dictService;

// 這里person從數(shù)據(jù)庫查詢出來的對象
PersonDTO person =  ……

// 數(shù)據(jù)字典翻譯
dictService.translationDictCode(person);

// translationDictCode 方法的實現(xiàn)
@Override
public void translationDictCode(Object obj) {
    DictUtil.translationDictCode(obj, this::queryDictMapData, false);
}

// 其中DictUtil.translationDictCode方的定義如下:
 /**
     * 將DTO對象(對象中含有有Dict注解的對象)的字典碼轉(zhuǎn)換為字典值名稱(注:通過Dict注解標識碼值的字段)
     *
     * @param obj                處理的對象
     * @param convertMapFun      字典類型和值的集合轉(zhuǎn)換為名稱
     * @param whetherConvertCode 是否轉(zhuǎn)換編碼
     */
    public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
                                              boolean whetherConvertCode) 

// 這里的queryDictMapData定義如下:做為translationDictCode中的入?yún)onvertMapFun,它是接收一個map困檩,然后返回另一個map祠挫,其中param是根據(jù)解析DTO上的Dict注解構(gòu)造出來的查詢參數(shù)。

/**
 * 查詢字典碼對應(yīng)的name
 */
private Map<String, String> queryDictMapData(Map<String, String> param) {
    // 調(diào)用接口根據(jù)入?yún)aram查詢數(shù)據(jù)字典翻譯出來的name值
    // 一般數(shù)據(jù)字典放在redis緩存中悼沿,統(tǒng)一一個地方處理等舔;這里可以根據(jù)這種入?yún)⑷ゾ彺嬷胁樵儯缓蠓庋b返回需要格式的數(shù)據(jù)
// 主要就是解析入?yún)⒃阒海缓髽?gòu)建特定格式的返回值慌植,比如:{"xx:certificateType=1": "身份證", "xx:certificateType=2": "護照"}
    return ……;
}

對queryDictMapData方法的入?yún)⒑头祷刂档慕庾x:

param 示例:{"relationship": "1,2", "AR:1001": "", ……}
其中key是字典type甚牲,value是要查詢的字典值,有多個則用逗號分隔蝶柿;
relationship對應(yīng)一個字典項列表丈钙,比如[{"1": "朋友"}, {"2", "同事"}, {"3", "親屬"}……]
而value說明要查詢字典code 1和2對應(yīng)的翻譯,那么返回結(jié)果如下:
因為字典type可能會有冒號字符交汤,這里用=做鏈接符
{"relationship=1": "朋友", "relationship=2": "同事"}

  1. DictUtil的部分方法
    首先從方法translationDictCode一步步來剖析:

其實一開始是沒有參數(shù)whetherConvertCode雏赦,后來為了兼容一種情況,就是先字典code的轉(zhuǎn)換(老系統(tǒng)的code轉(zhuǎn)換為新系統(tǒng)的code芙扎,反之亦然)星岗,然后再進行字典翻譯

public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
                                              boolean whetherConvertCode) {
        if (Objects.isNull(obj)) {
            return;
        }

        Map<String, String> fieldKeyMap = new HashMap<>();
        Map<String, String> keyCodeMap = new HashMap<>();

        // 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
        parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);

        // 字典值轉(zhuǎn)換(具體轉(zhuǎn)換的方法)
        Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);

        // 回填字典碼對應(yīng)的值(遞歸處理)
        backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
    }

除了對Object進行翻譯,直接對List<Object>也是可以的戒洼,方法如下:

    /**
     * 將集合中所有對象的字典碼轉(zhuǎn)換為字典值 (注:通過Dict注解標識碼值)
     *
     * @param list          處理的集合
     * @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
     * @author liam
     */
    public static void translationListCodeName(List list, Function<Map<String, String>, Map<String, String>> convertMapFun) {
        if (null == list || !list.isEmpty()) {
            return;
        }

        Map<String, String> fieldKeyMap = new HashMap<>();
        Map<String, String> keyCodeMap = new HashMap<>();
        // 構(gòu)造填充映射關(guān)系
        list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
        // 字典值轉(zhuǎn)換
        Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
        // 給field賦值
        list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
    }

完整的示例

  1. 測試類和運行結(jié)果
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

import java.util.List;

public class DictServiceTest {
    private static ObjectMapper objectMapper = new ObjectMapper();

    public static void main(String[] args) {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        DictService dictService = new DictService();

        PersonDTO person1 = new PersonDTO();
        person1.setCustCertificateType("1");
        PersonDTO person2 = new PersonDTO();
        person2.setCustCertificateType("2");
        person2.setCompanyProvince("10001");
        person2.setCompanyCity("1000101");
        List<PersonDTO> personList = Lists.newArrayList(person1, person2);

        System.out.println("轉(zhuǎn)換之前");
        personList.forEach(DictServiceTest::printJson);

        // 字典翻譯轉(zhuǎn)換
        dictService.translationListCodeName(personList);

        System.out.println("轉(zhuǎn)換之后");
        personList.forEach(DictServiceTest::printJson);
    }

    private static void printJson(Object obj) {
        try {
            System.out.println(objectMapper.writeValueAsString(obj));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

// 運行結(jié)果:
// 轉(zhuǎn)換之前
// {"custCertificateType":"1"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101"}
// 轉(zhuǎn)換之后
// {"custCertificateType":"1","custCertificateTypeName":"身份證"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101","custCertificateTypeName":"護照","companyProvinceName":"河北省","companyCityName":"邯鄲市"}
  1. DTO和常量類
/**
 * 數(shù)據(jù)字典相關(guān)常量
 *
 * @Author liam
 */
public interface DictConstant {

    /** “=”等號*/
    String SIGN_EQUAL = "=";
    /** “:”冒號 */
    String SIGN_COLON = ":";
    /** “,”逗號*/
    String SIGN_COMMA = ",";

    /**XX系統(tǒng)接口的數(shù)據(jù)字典前綴*/
    String DICT_SYSTEM_XX = "XX";
    /**省市區(qū)數(shù)據(jù)字典前綴*/
    String DICT_SYSTEM_AREA = "AR";

    /** XX系統(tǒng)數(shù)據(jù)字典 */
    String DICT_XX_COLON = DICT_SYSTEM_XX + SIGN_COLON;
    /** 省市區(qū)數(shù)據(jù)字典 */
    String DICT_AR_COLON = DICT_SYSTEM_AREA + SIGN_COLON;

    /** 學歷 */
    String DICT_EDUCATION = DICT_XX_COLON + "10001";
    /** 學位 */
    String DICT_DEGREE = DICT_XX_COLON + "10002";
    /** 證件類型 */
    String DICT_CERTIFICATE_TYPE = DICT_XX_COLON + "10003";

    /**省市區(qū)數(shù)據(jù)字典標識: 省*/
    String DICT_AR_PROVINCE = DICT_AR_COLON + "province";
}

import lombok.Data;
import static com.liam.dict.DictConstant.*;
@Data
public class PersonDTO {
    /** 證件類型(字典) */
    private String custCertificateType;
    /** 學歷(字典) */
    private String custEducation;
    /** 學位(字典) */
    private String custDegree;
    /** 單位所在省份(字典) */
    private String companyProvince;
    /** 單位所在城市(字典) */
    private String companyCity;
    /** 單位所在區(qū)/縣(字典) */
    private String companyDistrict;

    /** 證件類型(名稱) */
    @Dict(DICT_CERTIFICATE_TYPE)
    private String custCertificateTypeName;
    /** 學歷(名稱) */
    @Dict(DICT_EDUCATION)
    private String custEducationName;
    /** 學位(名稱) */
    @Dict(DICT_DEGREE)
    private String custDegreeName;

    /** 單位所在省份(名稱) */
    @Dict(value = DICT_AR_PROVINCE)
    private String companyProvinceName;
    /** 單位所在城市(名稱) */
    @Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyProvince")
    private String companyCityName;
    /** 單位所在區(qū)/縣(名稱) */
    @Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyCity")
    private String companyDistrictName;
}
  1. service類
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.liam.dict.DictConstant.*;

public class DictService {

    public void translationDictCode(Object obj) {
        DictUtil.translationDictCode(obj, this::queryDictMapData, false);
    }

    public void translationListCodeName(List list) {
        DictUtil.translationListCodeName(list, this::queryDictMapData);
    }

    /**
     * 查詢字典碼對應(yīng)的name
     */
    private Map<String, String> queryDictMapData(Map<String, String> typeCodeMap) {

        // 調(diào)用接口根據(jù)入?yún)aram查詢數(shù)據(jù)字典翻譯出來的name值
        // 一般數(shù)據(jù)字典放在redis緩存中俏橘,統(tǒng)一一個地方處理;這里可以根據(jù)這種入?yún)⑷ゾ彺嬷胁樵內剑缓蠓庋b返回需要格式的數(shù)據(jù)

        Map<String, String> result = new HashMap<>();
        if (typeCodeMap.isEmpty()) {
            return result;
        }

        // 獲取測試數(shù)據(jù)
        Map<String, List<DictDTO>> testData = getTestData();

        // 解析請求參數(shù)寥掐,構(gòu)造返回結(jié)果
        typeCodeMap.forEach((key, value) -> {
            // 查詢獲取字典項列表
            List<DictDTO> dictList = testData.get(key);
            // 需要翻譯的字典code
            String dictCode = typeCodeMap.get(key);
            if (dictCode.contains(SIGN_COMMA)) { // 逗號分隔
                Splitter.on(SIGN_COMMA)
                        .trimResults()
                        .splitToList(dictCode)
                        .forEach(code -> this.findSetCodeName(code, key, dictList, result));
            } else {
                this.findSetCodeName(dictCode, key, dictList, result);
            }
        });

        return result;
    }

    /**
     * 遍歷取出所有值比較code是否相同,如果找到結(jié)束循環(huán)汉额,取出結(jié)果
     *
     * @param dictCode : 字典code
     * @param hashKey : 字典碼
     * @param list : 字典集合
     * @param result : 最終結(jié)果集
     */
    private void findSetCodeName(String dictCode, Object hashKey,
                                 List<DictDTO> list, Map<String, String> result) {
        for (DictDTO dto : list) {
            if (dto.getCode().equals(dictCode)) {
                result.put(hashKey + SIGN_EQUAL + dictCode, dto.getName());
                break;
            }
        }
    }
    /**
     * 獲取測試數(shù)據(jù)
     */
    private Map<String, List<DictDTO>> getTestData() {
        return ImmutableMap.<String, List<DictDTO>>builder()
                .put(DICT_CERTIFICATE_TYPE, certificateTypeList)
                .put(DICT_AR_PROVINCE, provinceList)
                .put(DICT_AR_COLON + "10001", hebeiCityList)
                .build();
    }

    // 假如數(shù)據(jù)字典是如下列表

    /** 證件列表 */
    private List certificateTypeList = Lists.newArrayList(
        new DictDTO("1", "身份證"),
        new DictDTO("2", "護照"),
        new DictDTO("3", "駕駛證")
    );

    /** 省列表 */
    private List provinceList = Lists.newArrayList(
        new DictDTO("10001", "河北省"),
        new DictDTO("10002", "河南省"),
        new DictDTO("10003", "江蘇省")
    );

    /** 河北市列表 */
    private List hebeiCityList = Lists.newArrayList(
        new DictDTO("1000101", "邯鄲市"),
        new DictDTO("1000102", "秦皇島市")
    );

    /** 臨時測試數(shù)據(jù) */
    private Map<String, String> demoTestData = ImmutableMap.<String, String>builder()
            .put(DICT_CERTIFICATE_TYPE + SIGN_EQUAL + "1", "身份證")
            .put(DICT_CERTIFICATE_TYPE + SIGN_EQUAL + "2", "護照")
            .put(DICT_AR_PROVINCE + SIGN_EQUAL + "10001", "上海市")
            .put(DICT_AR_COLON + "10001" + SIGN_EQUAL + "1000101", "浦東新區(qū)")
            .build();
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DictDTO {
    /** 字典碼 */
    private String code;
    /** 字典名稱 */
    private String name;
}
  1. 工具類DictUtil曹仗,數(shù)據(jù)字典轉(zhuǎn)換的核心
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapperImpl;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.liam.dict.DictConstant.*;
/**
 * 數(shù)據(jù)字典工具類
 *
 * @Author libing
 */
@Slf4j
public class DictUtil {

    /**
     * Name后綴,默認的字典名稱字段命名規(guī)則
     */
    private static final String NAME_SUFFIX = "Name";

    /**
     * 將DTO對象(對象中含有有Dict注解的對象)的字典碼轉(zhuǎn)換為字典值名稱(注:通過Dict注解標識碼值的字段)
     *
     * @param obj                處理的對象
     * @param convertMapFun      字典類型和值的集合轉(zhuǎn)換為名稱
     * @param whetherConvertCode 是否轉(zhuǎn)換編碼
     */
    public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
                                           boolean whetherConvertCode) {
        if (Objects.isNull(obj)) {
            return;
        }

        Map<String, String> fieldKeyMap = new HashMap<>();
        Map<String, String> keyCodeMap = new HashMap<>();

        // 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
        parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);

        // 字典值轉(zhuǎn)換(具體轉(zhuǎn)換的方法)
        Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);

        /* 這里 keyCodeMap 蠕搜、 fieldKeyMap 怎茫、 convertedMap 三者之間的關(guān)系
         *      keyCodeMap 是字典type與字典code值的關(guān)系
         *          比如: {"xx:certificateType": "1,2"}
         *     convertedMap 外部方法處理的結(jié)果,字典翻譯的結(jié)果
         *          比如: {"xx:certificateType=1": "身份證", "xx:certificateType=2": "護照"}
         *     fieldKeyMap  是字典code所對應(yīng)的字段與轉(zhuǎn)換結(jié)果的關(guān)系
         *          比如: {"1:custCertificateType": "xx:certificateType=1", "2:custCertificateType": "xx:certificateType=2"}
         */

        // 回填字典碼對應(yīng)的值(遞歸處理)
        backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
    }

    /**
     * 將集合中所有對象的字典碼轉(zhuǎn)換為字典值 (注:通過Dict注解標識碼值)
     *
     * @param list          處理的集合
     * @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
     * @author liam
     */
    public static void translationListCodeName(List list, Function<Map<String, String>, Map<String, String>> convertMapFun) {
        if (null == list || list.isEmpty()) {
            return;
        }

        Map<String, String> fieldKeyMap = new HashMap<>();
        Map<String, String> keyCodeMap = new HashMap<>();
        // 構(gòu)造填充映射關(guān)系
        list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
        // 字典值轉(zhuǎn)換
        Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
        // 給field賦值
        list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
    }

    /**
     * 通過對Object的分析妓灌,構(gòu)造填充2種轉(zhuǎn)換關(guān)系map
     *
     * @param obj         : 取字段的對象
     * @param fieldKeyMap : (dictCode+字段名稱)和接口查詢返回的字典key的映射
     * @param keyCodeMap  : 用于接口查詢的字典key和code的映射
     * @author liam
     */
    private static void fillDictCodeConvertMap(Object obj, Map<String, String> fieldKeyMap, Map<String, String> keyCodeMap) {
        if (null == obj) { // 如果為空轨蛤,直接不做處理
            return;
        }

        Arrays.stream(obj.getClass().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Dict.class))
                .forEach(field -> parseDictAnnotation(obj, field, fieldKeyMap, keyCodeMap));
    }

    /**
     * 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
     *
     * @param obj                 : 取字段的對象
     * @param fieldKeyMap         : (dictCode+字段名稱)和接口查詢返回的字典key的映射
     * @param keyCodeMap          : 用于接口查詢的字典key和code的映射
     * @param whetherConvertCode: 是否轉(zhuǎn)換編碼
     * @author liam
     */
    private static void parseFieldCodeValue(Object obj, Map<String, String> fieldKeyMap, Map<String, String> keyCodeMap,
                                            boolean whetherConvertCode) {
        if (Objects.isNull(obj)) {
            return;
        }

        // 獲取當前對象所有的field
        Field[] declaredFields = obj.getClass().getDeclaredFields();

        // 構(gòu)造填充映射關(guān)系
        Arrays.stream(declaredFields).forEach(field ->
                parseFieldObj(field, obj, whetherConvertCode,
                        fieldObj -> parseFieldCodeValue(fieldObj, fieldKeyMap, keyCodeMap, whetherConvertCode), // 遞歸處理
                        // 解析注解字段信息
                        () -> parseDictAnnotation(obj, field, fieldKeyMap, keyCodeMap)
                )
        );
    }

    /**
     * 回填字典碼對應(yīng)的值(遞歸處理)
     *
     * @param obj          : 回填字段的對象
     * @param fieldKeyMap  : (dictCode+字段名稱)和接口查詢返回的字典key的映射
     * @param convertedMap : 字典值轉(zhuǎn)換后的map
     * @author liam
     */
    private static void backFillCodeName(Object obj, Map<String, String> fieldKeyMap, Map<String, String> convertedMap,
                                         boolean whetherConvertCode) {
        if (Objects.isNull(obj)) {
            return;
        }

        // 給field賦值
        Arrays.stream(obj.getClass().getDeclaredFields()).forEach(field ->
                parseFieldObj(field, obj, whetherConvertCode,
                        fieldObj -> backFillCodeName(fieldObj, fieldKeyMap, convertedMap, whetherConvertCode), // 遞歸處理
                        // 回填匹配的字段值
                        () -> setFieldValue(obj, field, fieldKeyMap, convertedMap)
                )
        );
    }

    /**
     * 解析field對象,對基本數(shù)據(jù)類型和復雜類型做不同的處理
     *
     * @param field               : 字段對象
     * @param obj                 : 字段所屬的obj對象
     * @param recursiveFunc       : 遞歸處理方法
     * @param parseAnnotationFunc : 核心解析有注解字段的方法
     * @author liam
     */
    private static void parseFieldObj(Field field, Object obj, boolean whetherConvertCode,
                                      Consumer<Object> recursiveFunc,
                                      NestedFunction parseAnnotationFunc) {
        Class cls = field.getType();
        if (Map.class.isAssignableFrom(cls)) {
            return; // map直接跳過
        }
        // 需要數(shù)據(jù)字典轉(zhuǎn)換的屬性:有Dict注解
        if (field.isAnnotationPresent(Dict.class)
                // 且匹配whetherConvertCode的值
                && field.getAnnotation(Dict.class).whetherConvertCode() == whetherConvertCode) {
            parseAnnotationFunc.run(); // 核心方法處理虫埂,
        }
        // 沒有注解的屬性判斷
        else {
            try {
                // 獲取字段值且非空處理
                field.setAccessible(true);
                Optional.ofNullable(field.get(obj)).ifPresent(fieldValue -> {
                            // 集合類型祥山,如果泛型的類型是JavaBean,繼續(xù)遞歸處理
                            if (Collection.class.isAssignableFrom(cls)) {
                                // 如果是list-map結(jié)果掉伏,則這里返回null
                                Class generic = getGeneric(obj.getClass(), field.getName());
                                if (null != generic && notInFilterClass(generic)) {
                                    // 循環(huán)遞歸處理
                                    ((Collection) fieldValue).forEach(recursiveFunc::accept);
                                }
                            }
                            // 非基本數(shù)據(jù)類型
                            else if (notInFilterClass(cls)) {
                                recursiveFunc.accept(fieldValue);
                            }
                        }
                );

            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 解析含有注解Dict的field信息缝呕,并將其充到2種轉(zhuǎn)換關(guān)系map中
     *
     * @param obj         : 對象
     * @param field       : 字段
     * @param fieldKeyMap : (獲取dictCode+字段名稱)和接口查詢返回的字典key的映射
     * @param keyCodeMap  : 用于接口查詢的字典key和code的映射
     * @author liam
     */
    private static void parseDictAnnotation(Object obj, Field field,
                                            Map<String, String> fieldKeyMap,
                                            Map<String, String> keyCodeMap) {
        // 讀取注解信息,獲取編碼類型
        Dict dict = field.getAnnotation(Dict.class);

        String fieldName = field.getName();

        // 根據(jù)Dict的codeName屬性或者字段名稱斧散,獲取字典編碼code
        String code = getFieldValue(obj, dict, fieldName);
        if (!Strings.isNullOrEmpty(code)) {
            String dictType = dict.value();
            // 獲取dictCode+字段名稱的組合
            fieldName = getDictCodeCombineName(code, fieldName);
            // 二級編碼字段
            if (dict.isChild()) {
                // 獲取二級字段的字典編碼值
                String parentCode = getPropertyValue(obj, dict.parentField());
                // 父級code
                String key = dictType + SIGN_COLON + parentCode;
                // 如果有相同的父級code供常,且code不重復
                if (null != keyCodeMap.get(key) && !code.equals(keyCodeMap.get(key))) {
                    keyCodeMap.put(key, keyCodeMap.get(key) + SIGN_COMMA + code);
                } else {
                    keyCodeMap.put(key, code);
                }
                // 屬性名對應(yīng)的 字典碼和code
                fieldKeyMap.put(fieldName, key + SIGN_EQUAL + code);
            } else {
                // 有多個相同的類型,且code不重復
                if (null != keyCodeMap.get(dictType) && !code.equals(keyCodeMap.get(dictType))) {
                    keyCodeMap.put(dictType, keyCodeMap.get(dictType) + SIGN_COMMA + code);
                } else {
                    keyCodeMap.put(dictType, code);
                }
                // 屬性名對應(yīng)的 字典碼和code
                fieldKeyMap.put(fieldName, dictType + SIGN_EQUAL + code);
            }
        }
    }

    /**
     * 給一個對象所有的含有Dict注解的字段賦值
     *
     * @param obj          : 取字段的對象
     * @param fieldKeyMap  : (dictCode+字段名稱)和接口查詢返回的字典key的映射
     * @param convertedMap : 字典值轉(zhuǎn)換后的map
     * @author liam
     */
    private static void setObjAllFieldValue(Object obj, Map<String, String> fieldKeyMap, Map<String, String> convertedMap) {
        if (null == obj) { // 如果為空鸡捐,直接不做處理
            return;
        }

        Arrays.stream(obj.getClass().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Dict.class))
                .forEach(field -> setFieldValue(obj, field, fieldKeyMap, convertedMap));
    }

    /**
     * 給fiel設(shè)置值
     *
     * @param obj          : 對象
     * @param field        : 字段
     * @param fieldKeyMap  : 字段值映射關(guān)系
     * @param convertedMap :轉(zhuǎn)換碼關(guān)系
     * @author liam
     */
    private static void setFieldValue(Object obj, Field field, Map<String, String> fieldKeyMap,
                                      Map<String, String> convertedMap) {
        // 根據(jù)字段code和字段名稱構(gòu)建組合名稱
        String fieldName = field.getName();
        Dict dictAnnotation = field.getAnnotation(Dict.class);
        String fieldValue = getFieldValue(obj, dictAnnotation, fieldName);
        String combineFieldName = getDictCodeCombineName(fieldValue, fieldName);

        Optional.ofNullable(fieldKeyMap.get(combineFieldName)).ifPresent(dicKey -> {
            String convertedValue = convertedMap.get(dicKey); // 轉(zhuǎn)換后的值

            try {
                field.setAccessible(true);
                // 當轉(zhuǎn)換后的值為null栈暇,判斷是否需要將null值覆蓋原有的值
                if (null == convertedValue) {
                    if (dictAnnotation.whetherSetIfNull()) {
                        field.set(obj, null);
                    }
                    return;
                }

                field.set(obj, convertedValue);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        });
    }

    /**
     * 獲取對象里指定屬性的值,并轉(zhuǎn)化為字符串
     *
     * @param obj          : 對象
     * @param propertyName : 對象里面的屬性名稱
     * @author liam
     */
    private static String getPropertyValue(Object obj, String propertyName) {
        BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
        if (beanWrapper.isReadableProperty(propertyName)) {
            Object propertyValue = beanWrapper.getPropertyValue(propertyName);
            if (null != propertyValue) {
                return propertyValue.toString();
            }
        }
        return "";
    }

    /**
     * 根據(jù)Dict的codeName屬性或者字段名稱箍镜,獲取字段值 <br/>
     * <pre>
     *  注意:
     *     如果當前字段沒有以Name結(jié)尾源祈,那就取當前字段的值煎源;也就是根據(jù)當前字段的值轉(zhuǎn)換。
     * </pre>
     *
     * @param obj       : 對象
     * @param dict      : 字段注解對象
     * @param fieldName : 字段名稱
     * @return java.lang.String
     * @author liam
     */
    private static String getFieldValue(Object obj, Dict dict, String fieldName) {
        String codeName = dict.codeName();
        if (Strings.isNullOrEmpty(codeName)) {
            // 如果當前字段是Name結(jié)尾香缺,進行截仁窒;否則取當前字段名稱
            int endNameIndex = fieldName.lastIndexOf(NAME_SUFFIX);
            if (endNameIndex != -1) {
                codeName = fieldName.substring(0, endNameIndex);
            } else {
                codeName = fieldName;
            }
        }
        return getPropertyValue(obj, codeName);
    }

    /**
     * 獲取dictCode+字段名稱的組合
     *
     * @param dictCode  : 字段映射的字典碼值
     * @param fieldName : 字段名稱
     * @return dictCode:fieldName
     * @author liam
     */
    private static String getDictCodeCombineName(String dictCode, String fieldName) {
        // 為了解決list下的fieldName相同導致fieldKeyMap的key相同图张,添加dictCode唯一區(qū)分
        return dictCode + SIGN_COLON + fieldName;
    }

    /**
     * 獲取一個類的屬性的泛型原献;如果沒有泛型,則返回null埂淮;
     * P.s 如果有多個,取第一個写隶;如果有多層泛型倔撞,也返回null,比如List<Map>
     *
     * @param cls      :
     * @param property : 屬性名
     * @author liam
     */
    public static Class getGeneric(Class cls, String property) {
        try {
            Type genericType = cls.getDeclaredField(property).getGenericType();
            // 如果是泛型參數(shù)的類型
            if (null != genericType && genericType instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) genericType;
                Type type = pt.getActualTypeArguments()[0];
                // 這里慕趴,type也可能是 ParameterizedType痪蝇, 直接不考慮
                if (type instanceof Class) {
                    return (Class) type;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 判斷不在過濾類(常用基本數(shù)據(jù)類型)中
     */
    private static boolean notInFilterClass(Class cls) {
        return !Lists.newArrayList(String.class, BigDecimal.class, Date.class,
                Integer.class, int.class,
                Double.class, double.class,
                Boolean.class, boolean.class,
                Long.class, long.class,
                Character.class, char.class)
                .contains(cls);
    }

    /**
     * 函數(shù)式接口:類似freemarker中的<#nested>處理
     */
    @FunctionalInterface
    public interface NestedFunction {
        /**
         * 無參無返回值的方法運行
         */
        void run();
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者冕房。
  • 序言:七十年代末躏啰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耙册,更是在濱河造成了極大的恐慌给僵,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件详拙,死亡現(xiàn)場離奇詭異帝际,居然都是意外死亡,警方通過查閱死者的電腦和手機饶辙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門蹲诀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弃揽,你說我怎么就攤上這事脯爪。” “怎么了矿微?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵痕慢,是天一觀的道長。 經(jīng)常有香客問我冷冗,道長守屉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任蒿辙,我火速辦了婚禮拇泛,結(jié)果婚禮上滨巴,老公的妹妹穿的比我還像新娘。我一直安慰自己俺叭,他們只是感情好恭取,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著熄守,像睡著了一般蜈垮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裕照,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天攒发,我揣著相機與錄音,去河邊找鬼晋南。 笑死惠猿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的负间。 我是一名探鬼主播偶妖,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼政溃!你這毒婦竟也來了趾访?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤董虱,失蹤者是張志新(化名)和其女友劉穎扼鞋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體空扎,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡藏鹊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了转锈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盘寡。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撮慨,靈堂內(nèi)的尸體忽然破棺而出竿痰,到底是詐尸還是另有隱情,我是刑警寧澤砌溺,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布影涉,位于F島的核電站,受9級特大地震影響规伐,放射性物質(zhì)發(fā)生泄漏蟹倾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲜棠。 院中可真熱鬧肌厨,春花似錦、人聲如沸豁陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盒音。三九已至表鳍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祥诽,已是汗流浹背譬圣。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雄坪,地道東北人胁镐。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像诸衔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颇玷,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容