java腦洞 效率最高和最懂國人的對象轉換工具 lp-converter-processor

腦洞的由來

開發(fā)過程中經(jīng)常遇到

  1. 把EntityA的屬性賦值給新的EntityB
  2. 把Entity的屬性轉換成Map結構
  3. 把Map結構的數(shù)據(jù)轉換成Entity

當前常見解決方案

  1. 把Entity轉換成json,再把json轉換成目標Entity
  2. 第三方工具MapStruct
  3. spring的BeanUtils

效率對比

  1. json轉換和BeanUtils都是基于Class的反射映射琴锭,效率不高
  2. MapStruct是根據(jù)注解生成字節(jié)碼钱反,效率高畔况,但是不夠靈活丈氓,而且沒法滿足場景二和場景三

介紹 lp-converter-processor

  1. 基于注解生成字節(jié)碼局冰,效率高蔚润。
  2. 基于jdk8的接口default機制實現(xiàn)靈活編入
  3. 有字段檢查機制,提供安全檢查能力
  4. 能兼容lombok
  5. 國人編寫(doge 最懂國人的由來)

開始使用

1. jdk 要求 8+

2. 引入maven

        <dependency>
            <groupId>io.github.wqr503</groupId>
            <artifactId>lp-converter-processor</artifactId>
            <version>2.0.4</version>
        </dependency>

3. 基于注解@ConverterMapper和接口MapConverter和接口BeanConverter實現(xiàn)

  1. MapConverter 接口
  public interface MapConverter<S> extends Converter{

    // 轉換 map - S
    default S convertFromMap(Map<String,Object> dataSource) {
      return null;    
    }

    //轉換Map S -> map
    default Map<String,Object> convertToMap(S source) {
      return null;    
    }

    default Map<String,Object> postConvertToMap(S source, Map<String,Object> dataSource) {
        return dataSource;
    }

    default S preConvertFromMap(Map<String,Object> dataSource, S source) {
        return source;
    }

    default S postConvertFromMap(Map<String,Object> dataSource, S source) {
        return source;
    }

}

主要是convertFromMap 和 convertToMap 接口既棺,convertFromMap 實現(xiàn)把Map結構的數(shù)據(jù)轉換成Entity讽挟, convertToMap 實現(xiàn) 把Entity的屬性轉換成Map結構

  1. BeanConverter接口
/**
 * 對象轉換
 */
public interface BeanConverter<S, T> extends Converter{

    //轉換 S - T
    default T convertTo(S source) {
      return null;    
    }

    //合并 S - T
   default MergeResult<T> mergeTo(S source, T target) {
      return new MergeResult<>();    
    }

    default T postConvertTo(S source, T target) {
        return target;
    }

    default MergeResult<T> postMergeTo(S source, T target, MergeResult<T> result) {
        return result;
    }

}

主要是 convertTo 和 mergeTo 接口,convertTo 實現(xiàn)把EntityA 生成 新的EntityB丸冕, mergeTo 實現(xiàn) 把EntityA 合并到 EntityB耽梅,返回合并后的EntityB, MergeResult中包含是否有改變胖烛,和改變的字段

/**
 * 合并結果
 */
public class MergeResult<T> {

    /** 合并結果 */
    private T entity;

    /** 是否有改變 */
    private boolean change = false;

    /** 合并字段 */
    private Set<String> changeFieldNames = new HashSet<>();

}

4. 實戰(zhàn)

1. 最簡單的Bean -> Bean轉換
    /**
     * 最簡單的Bean -> Bean轉換
     * MergeResult : 兩個對象合并的結果
     *   change : 是否有變化
     *   changeFieldNames : target有賦值的字段
     *   entity : target對象
     */
@ConverterMapper
public interface TestBeanConverter extends BeanConverter<TestBeanConverter.EntityA, TestBeanConverter.EntityB> {

    @Data
    class EntityA {

        private HashMap<String, Integer> mapData;

        private int age;

    }

    @Data
    class EntityB {

        private Map<String, Integer> mapData;

        private long age;

    }

}
    private void testBaseBeanConverter() {
        TestBeanConverter.EntityA entityA = new TestBeanConverter.EntityA();
        entityA.setAge(1);
        HashMap<String, Integer> mapData = new HashMap<>();
        mapData.put("test", 123);
        entityA.setMapData(mapData);
        TestBeanConverter converter = ConverterHolder.getConverter(TestBeanConverter.class);
        TestBeanConverter.EntityB entityB = converter.convertTo(entityA);
        System.out.println("testBaseBeanConverter ---------------- 舊對象A:" + JSON.toJSONString(entityA));
        System.out.println("testBaseBeanConverter ---------------- 新對象:" + JSON.toJSONString(entityB));
        System.out.println("");
        MergeResult<TestBeanConverter.EntityB> entityBMergeResult = converter.mergeTo(entityA, entityB);
        System.out.println("testBaseBeanConverter ---------------- MergeResult:" + JSON.toJSONString(entityBMergeResult));
        System.out.println("");
    }

結果

testBaseBeanConverter ---------------- 舊對象A:{"age":1,"mapData":{"test":123}}
testBaseBeanConverter ---------------- 新對象:{"age":0,"mapData":{"test":123}}

testBaseBeanConverter ---------------- MergeResult:{"change":true,"changeFieldNames":["mapData"],"entity":{"age":0,"mapData":{"test":123}}}
2. 最簡單的Bean -> Map轉換
    /**
     * 最簡單的Bean -> Map轉換
     */
@ConverterMapper
public interface TestMapConverter extends MapConverter<TestMapConverter.EntityA> {

    @Data
    class EntityA {

        private HashMap<String, Integer> mapData;

        private int age;

    }

}
    private void testBaseMapConverter() {
        TestMapConverter.EntityA entityA = new TestMapConverter.EntityA();
        entityA.setAge(1);
        HashMap<String, Integer> mapData = new HashMap<>();
        mapData.put("test", 123);
        entityA.setMapData(mapData);
        TestMapConverter converter = ConverterHolder.getConverter(TestMapConverter.class);
        Map<String, Object> entityMap = converter.convertToMap(entityA);
        System.out.println("testBaseMapConverter ---------------- 舊對象:" + JSON.toJSONString(entityA));
        System.out.println("testBaseMapConverter ---------------- 新對象:" + JSON.toJSONString(entityMap));
        System.out.println("");
    }

結果

testBaseMapConverter ---------------- 舊對象:{"age":1,"mapData":{"test":123}}
testBaseMapConverter ---------------- 新對象:{"mapData":{"test":123},"age":1}
3. 配合spring注解注入使用
    /**
     * 配合spring注解注入使用眼姐,implSpringInterface 為 true
     */
@ConverterMapper(implSpringInterface = true)
public interface TestComponentConverter extends MapConverter<TestComponentConverter.EntityA> {

    @Data
    class EntityA {

        private HashMap<String, Integer> mapData;

        private int age;

    }

}
    @Resource
    private TestComponentConverter testComponentConverter;

    private void testComponentConverter() {
        TestComponentConverter.EntityA entityA = new TestComponentConverter.EntityA();
        entityA.setAge(1);
        HashMap<String, Integer> mapData = new HashMap<>();
        mapData.put("test", 123);
        entityA.setMapData(mapData);
        Map<String, Object> entityMap = testComponentConverter.convertToMap(entityA);
        System.out.println("testComponentConverter ---------------- 舊對象:" + JSON.toJSONString(entityA));
        System.out.println("testComponentConverter ---------------- 新對象:" + JSON.toJSONString(entityMap));
        System.out.println("");
    }

結果

testComponentConverter ---------------- 舊對象:{"age":1,"mapData":{"test":123}}
testComponentConverter ---------------- 新對象:{"mapData":{"test":123},"age":1}
4. ConverterMapper 參數(shù)
    /**
     * ConverterMapper 參數(shù)
     * ignoreEmpty - 忽略空值,如果是空值則不賦值, 為true, entityB的EmptyData會保留hasData值佩番,為false众旗,則被替換為null
     * ignoreGenericType - 忽略泛型,Map<String, Integer> 能和 Map 匹配
     * matchType -  為true趟畏,則listData不會生成贡歧,為false,則listData會生成, 并且會報錯赋秀,類型轉換錯誤
     * reNameField - 字段名別名映射
     * fieldNameFilter - 字段過濾(name利朵,注釋,正則)指定字段生效或者不生效
     */
@ConverterMapper(ignoreEmpty = true, ignoreGenericType = true,
    fieldNameFilter = @FieldNameFilter(assignIgnoreRegexFieldName = "ignore*"),
    reNameField = @ReNameField(sourceName = "oldName", targetName = "newName"))
public interface TestMapperFieldConverter extends BeanConverter<TestMapperFieldConverter.EntityA, TestMapperFieldConverter.EntityB> {

    @Data
    class EntityA {

        private Set<String> listData;

        private Map<String, Integer> mapData;

        private int age;

        private String oldName;

        private String emptyData;

        private String ignoreFiled;

    }

    @Data
    class EntityB {

        private List<String> listData;

        private Map mapData;

        private Long age;

        private String newName;

        private String emptyData;

        private String ignoreFiled;
    }

}
private void testMapperFieldConverter() {
        TestMapperFieldConverter.EntityA entityA = new TestMapperFieldConverter.EntityA();
        entityA.setAge(1);
        entityA.setListData(Sets.newHashSet("123"));
        entityA.setOldName("123");
        entityA.setIgnoreFiled("nameA");
        HashMap<String, Integer> mapData = new HashMap<>();
        mapData.put("test", 123);
        entityA.setMapData(mapData);
        TestMapperFieldConverter converter = ConverterHolder.getConverter(TestMapperFieldConverter.class);
        TestMapperFieldConverter.EntityB entityB = new TestMapperFieldConverter.EntityB();
        entityB.setEmptyData("hasData");
        entityB.setIgnoreFiled("nameB");
        System.out.println("testMapperFieldConverter ---------------- 舊對象A:" + JSON.toJSONString(entityA));
        System.out.println("testMapperFieldConverter ---------------- 舊對象B:" + JSON.toJSONString(entityB));
        converter.mergeTo(entityA, entityB);
        System.out.println("testMapperFieldConverter ---------------- 新對象B:" + JSON.toJSONString(entityB));
        System.out.println("");
    }

結果

testMapperFieldConverter ---------------- 舊對象A:{"age":1,"ignoreFiled":"nameA","listData":["123"],"mapData":{"test":123},"oldName":"123"}
testMapperFieldConverter ---------------- 舊對象B:{"emptyData":"hasData","ignoreFiled":"nameB"}
testMapperFieldConverter ---------------- 新對象B:{"emptyData":"hasData","ignoreFiled":"nameB","mapData":{"test":123},"newName":"123"}
5. ConverterMapping 參數(shù)
    /**
     * ConverterMapping 參數(shù)
     * 注釋在convertFromMap猎莲,convertToMap绍弟, convertTo, mergeTo 方法上著洼,覆蓋ConverterMapper配置樟遣,要顯示配置參數(shù),默認值默認不生效
     * ignoreEmpty - 忽略空值郭脂,如果是空值則不賦值, 為true, entityB的EmptyData會保留hasData值年碘,為false,則被替換為null
     * ignoreGenericType - 忽略泛型展鸡,Map<String, Integer> 能和 Map 匹配
     * matchType -  為true,則listData不會生成埃难,為false莹弊,則listData會生成, 并且會報錯,類型轉換錯誤
     * reNameField - 字段名別名映射
     * fieldNameFilter - 字段過濾(name涡尘,注釋忍弛,正則)指定字段生效或者不生效
     */
@ConverterMapper(ignoreEmpty = true, ignoreGenericType = true,
    fieldNameFilter = @FieldNameFilter(assignIgnoreRegexFieldName = "ignore*"),
    reNameField = @ReNameField(sourceName = "oldName", targetName = "newName"))
public interface TestMappingFieldConverter extends BeanConverter<TestMappingFieldConverter.EntityA, TestMappingFieldConverter.EntityB> {

    @Data
    class EntityA {

        private Set<String> listData;

        private Map<String, Integer> mapData;

        private int age;

        private String oldName;

        private String emptyData;

        private String ignoreFiled;

    }

    @Data
    class EntityB {

        private List<String> listData;

        private Map mapData;

        private Long age;

        private String newName;

        private String emptyData;

        private String ignoreFiled;
    }

    @Override
    @ConverterMapping(ignoreEmpty = false, fieldNameFilter = @FieldNameFilter(assignFieldName = "emptyData", assignRegexFieldName = "ignore*"))
    MergeResult<EntityB> mergeTo(EntityA source, EntityB target);

}
private void testMappingFieldConverter() {
        TestMappingFieldConverter.EntityA entityA = new TestMappingFieldConverter.EntityA();
        entityA.setAge(1);
        entityA.setListData(Sets.newHashSet("123"));
        entityA.setOldName("123");
        entityA.setIgnoreFiled("nameA");
        HashMap<String, Integer> mapData = new HashMap<>();
        mapData.put("test", 123);
        entityA.setMapData(mapData);
        TestMappingFieldConverter converter = ConverterHolder.getConverter(TestMappingFieldConverter.class);
        TestMappingFieldConverter.EntityB entityB = new TestMappingFieldConverter.EntityB();
        entityB.setEmptyData("hasData");
        entityB.setIgnoreFiled("nameB");
        System.out.println("testMappingFieldConverter ---------------- 舊對象A:" + JSON.toJSONString(entityA));
        System.out.println("testMappingFieldConverter ---------------- 舊對象B:" + JSON.toJSONString(entityB));
        converter.mergeTo(entityA, entityB);
        System.out.println("testMappingFieldConverter ---------------- 新對象B:" + JSON.toJSONString(entityB));
        System.out.println("");
    }

結果

testMappingFieldConverter ---------------- 舊對象A:{"age":1,"ignoreFiled":"nameA","listData":["123"],"mapData":{"test":123},"oldName":"123"}
testMappingFieldConverter ---------------- 舊對象B:{"emptyData":"hasData","ignoreFiled":"nameB"}
testMappingFieldConverter ---------------- 新對象B:{"ignoreFiled":"nameA"}
6. MapConverter指定方法
 /**
     * MapConverter指定方法
     * JudgeEmptyMethod - 判斷參數(shù)是否為空,返回值為boolean考抄,匹配規(guī)則:入?yún)㈩愋秃蚐ource的類型
     * DefaultValueMethod - 默認值细疚,如果參數(shù)為空,則修改為默認值川梅,匹配規(guī)則:返回的類型和Source參數(shù)的類型
     * TypeChangeMethod - 類型轉換疯兼,比如int -> String然遏, 匹配規(guī)則:參數(shù)1的類型和Source參數(shù)類型,返回類型和target參數(shù)類型
     * postConvertToMap - ConvertToMap 之后觸發(fā), 能改變最終返回的Map<String, Object>
     * preConvertFromMap - ConvertFromMap 之前觸發(fā)
     * postConvertFromMap - ConvertFromMap 之后觸發(fā)吧彪, 能改變最終返回的Entity
     *
     * 參數(shù)說明:
     * fieldNameFilter - 字段過濾(name待侵,注釋,正則)指定字段生效或者不生效
     * primary - 多個規(guī)則生效的情況下姨裸,提高優(yōu)先度秧倾, 默認前面的方法優(yōu)先度更高(不能完全保證),所以primary最好只有一個
     *
     * 特殊規(guī)則 :
     * 1. 由于fromMap方式入?yún)⑹荗bject傀缩,所以要特殊處理:
     *  1)assignFromMap 為true時那先, 非fromMap方法不生效,
     *  2)assignFromMap 為false時(默認)赡艰,assignFieldName在fromMap方法不生, 其他屬性則無影響都會生效
     *  3)由于入?yún)⑹荗bject, fromMap會先強制轉換成target對應字段的類型售淡,所以DefaultValueMethod,JudgeEmptyMethod
     * 是根據(jù)target的參數(shù)類型匹配的瞄摊,而TypeChangeMethod則不受影響勋又,還是根據(jù)參數(shù)1類型是Object,返回類型和target參數(shù)類型
     *
     * 2. ConverterMapper中的defaultValue如果為false换帜,則DefaultValueMethod不生效
     *
     * 3. ConverterMapper中的ignoreEmpty如果為false楔壤,但是如果存在DefaultValueMethod或者JudgeEmptyMethod
     * 方法,則仍然生效
     *
     * 4. 所有方法的類型匹配都會遞歸向父類索引惯驼,也就是HashMap找不到則會找Map蹲嚣,最后到Object
     */
@ConverterMapper
public interface TestMapMethodConverter extends MapConverter<TestMapMethodConverter.EntityA> {

    @Data
    class EntityA {

        private HashMap<String, Integer> emptyData;

        private int changeData;

        private int assignData;

        private long notChangeData;

        private int fromMapData;

    }

    @TypeChangeMethod(assignFromMap = true)
    default int fromMapChangeType(Object data) {
        return 10086;
    }

    @JudgeEmptyMethod
    default boolean judgeEmpty(HashMap<String, Integer> map) {
        return map == null || map.size() <= 0;
    }

    @DefaultValueMethod
    default HashMap<String, Integer> defaultValue() {
        HashMap<String, Integer> dataMap = new HashMap<>();
        dataMap.put("defaultValue", 0);
        return dataMap;
    }

    @TypeChangeMethod
    default Object changeType(int obj) {
        return "changeValue";
    }

    @TypeChangeMethod(fieldNameFilter = @FieldNameFilter(assignFieldName = "assignData"))
    default String assignChangeType(int obj) {
        return "assignValue";
    }

    @Override
    default Map<String, Object> postConvertToMap(TestMapMethodConverter.EntityA source, Map<String, Object> dataSource) {
        System.out.println("ConvertToMap 之后觸發(fā)");
        return dataSource;
    }

    @Override
    default TestMapMethodConverter.EntityA preConvertFromMap(Map<String, Object> dataSource, TestMapMethodConverter.EntityA source) {
        System.out.println("ConvertFromMap 之前觸發(fā)");
        return source;
    }

    @Override
    default EntityA postConvertFromMap(Map<String, Object> dataSource, EntityA source) {
        System.out.println("ConvertFromMap 之后觸發(fā)");
        return source;
    }
}
    private void testMapMethodConverter() {
        TestMapMethodConverter.EntityA entityA = new TestMapMethodConverter.EntityA();
        entityA.setChangeData(100);
        entityA.setNotChangeData(200);
        entityA.setAssignData(300);
        entityA.setFromMapData(1000);
        TestMapMethodConverter converter = ConverterHolder.getConverter(TestMapMethodConverter.class);
        System.out.println("testMapMethodConverter(toMap) ---------------- 舊對象A:" + JSON.toJSONString(entityA));
        Map<String, Object> dataMap = converter.convertToMap(entityA);
        System.out.println("testMapMethodConverter(toMap) ---------------- 新對象B:" + JSON.toJSONString(dataMap));
        System.out.println("");
        System.out.println("testMapMethodConverter(FromMap) ---------------- 舊對象Map:" + JSON.toJSONString(dataMap));
        TestMapMethodConverter.EntityA newEntityA = converter.convertFromMap(dataMap);
        System.out.println("testMapMethodConverter(FromMap) ---------------- 新對象:" + JSON.toJSONString(newEntityA));
    }

結果

testMapMethodConverter(toMap) ---------------- 舊對象A:{"assignData":300,"changeData":100,"fromMapData":1000,"notChangeData":200}
ConvertToMap 之后觸發(fā)
testMapMethodConverter(toMap) ---------------- 新對象B:{"assignData":"assignValue","notChangeData":200,"emptyData":{"defaultValue":0},"changeData":"changeValue","fromMapData":"changeValue"}
testMapMethodConverter(FromMap) ---------------- 舊對象Map:{"assignData":"assignValue","notChangeData":200,"emptyData":{"defaultValue":0},"changeData":"changeValue","fromMapData":"changeValue"}
ConvertFromMap 之前觸發(fā)
ConvertFromMap 之后觸發(fā)
testMapMethodConverter(FromMap) ---------------- 新對象:{"assignData":10086,"changeData":10086,"emptyData":{"defaultValue":0},"fromMapData":10086,"notChangeData":200}

7. BeanConverter指定方法
/**
     * BeanConverter指定方法
     * JudgeEmptyMethod - 判斷參數(shù)是否為空,返回值為boolean祟牲,匹配規(guī)則:入?yún)㈩愋秃蚐ource的類型
     * DefaultValueMethod - 默認值隙畜,如果參數(shù)為空,則修改為默認值说贝,匹配規(guī)則:返回的類型和Source參數(shù)的類型
     * TypeChangeMethod - 類型轉換议惰,比如int -> String, 匹配規(guī)則:參數(shù)1的類型和Source參數(shù)類型乡恕,返回類型和target參數(shù)類型
     * judgeSame - 判斷是否相同言询,如果相同則跳過字段,匹配規(guī)則: 參數(shù)1的類型和Source參數(shù)類型傲宜,參數(shù)2的類型和target參數(shù)類型
     * postMergeTo - ConvertTo 之后觸發(fā)运杭, 能改變最終返回的Entity
     * postConvertTo - MergeTo 之后觸發(fā), 能改變最終返回的Entity
     *
     * 參數(shù)說明:
     * fieldNameFilter - 字段過濾(name函卒,注釋辆憔,正則)指定字段生效或者不生效
     * primary - 多個規(guī)則生效的情況下,提高優(yōu)先度, 默認前面的方法優(yōu)先度更高(不能完全保證)虱咧,所以primary最好只有一個
     *
     * 特殊規(guī)則 :
     * 1. 由于這里沒有fromMap方法熊榛,assignFromMap保持為false, 如果設為true則導致該方法不生效
     *
     * 2. ConverterMapper中的defaultValue如果為false,則DefaultValueMethod不生效
     *
     * 3. ConverterMapper中的ignoreEmpty如果為false彤钟,但是如果存在DefaultValueMethod或者JudgeEmptyMethod
     * 方法来候,則仍然生效
     *
     * 4. judgeSame的前提是target和source的類型是一致或者存在TypeChangeMethod
     *
     * 5. 所有方法的類型匹配都會遞歸向父類索引,也就是HashMap找不到則會找Map逸雹,最后到Object
     */
@ConverterMapper
public interface TestBeanMethodConverter extends BeanConverter<TestBeanMethodConverter.EntityA, TestBeanMethodConverter.EntityB> {

    @Data
    class EntityA {

        private HashMap<String, Integer> emptyData;

        private int changeData;

        private int assignData;

        private long notChangeData;

        private String sameData;

        private String notSameData;

    }

    @Data
    class EntityB {

        private HashMap<String, Integer> emptyData;

        private int changeData;

        private String assignData;

        private long notChangeData;

        private int sameData;

        private long notSameData;

    }

    @TypeChangeMethod
    default long notSameChangeType(String obj) {
        return 2999;
    }

    @JudgeSameMethod
    default boolean judgeSame(String param1, long param2) {
        return false;
    }

    @JudgeSameMethod
    default boolean judgeSame(String param1, int param2) {
        return true;
    }

    @TypeChangeMethod
    default int sameChangeType(String obj) {
        return 10086;
    }

    @JudgeEmptyMethod
    default boolean judgeEmpty(HashMap<String, Integer> map) {
        return map == null || map.size() <= 0;
    }

    @DefaultValueMethod
    default HashMap<String, Integer> defaultValue() {
        HashMap<String, Integer> dataMap = new HashMap<>();
        dataMap.put("defaultValue", 0);
        return dataMap;
    }

    @TypeChangeMethod(fieldNameFilter = @FieldNameFilter(assignFieldName = "assignData"))
    default String assignChangeType(int obj) {
        return "assignValue";
    }

@Override
    default EntityB postConvertTo(EntityA source, EntityB target) {
        System.out.println("ConvertTo 之后觸發(fā)");
        return target;
    }

    @Override
    default MergeResult<EntityB> postMergeTo(EntityA source, EntityB target, MergeResult<EntityB> result) {
        System.out.println("MergeTo 之后觸發(fā)");
        return result;
    }

}
private void testBeanMethodConverter() {
        TestBeanMethodConverter.EntityA entityA = new TestBeanMethodConverter.EntityA();
        entityA.setChangeData(100);
        entityA.setNotChangeData(200);
        entityA.setAssignData(300);
        entityA.setSameData("same");
        entityA.setNotSameData("notSame");
        TestBeanMethodConverter.EntityB entityB = new TestBeanMethodConverter.EntityB();
        entityB.setSameData(1);
        entityB.setNotSameData(2);
        TestBeanMethodConverter converter = ConverterHolder.getConverter(TestBeanMethodConverter.class);
        System.out.println("testBeanMethodConverter---------------- 舊對象A:" + JSON.toJSONString(entityA));
        System.out.println("testBeanMethodConverter---------------- 舊對象B:" + JSON.toJSONString(entityB));
        MergeResult<TestBeanMethodConverter.EntityB> entityBMergeResult = converter.mergeTo(entityA, entityB);
        System.out.println("testBeanMethodConverter ---------------- 新對象B:" + JSON.toJSONString(entityBMergeResult));
        System.out.println("");
    }

結果

testBeanMethodConverter---------------- 舊對象A:{"assignData":300,"changeData":100,"notChangeData":200,"notSameData":"notSame","sameData":"same"}
testBeanMethodConverter---------------- 舊對象B:{"changeData":0,"notChangeData":0,"notSameData":2,"sameData":1}
MergeTo 之后觸發(fā)
testBeanMethodConverter ---------------- 新對象B:{"change":true,"changeFieldNames":["assignData","notChangeData","emptyData","changeData","notSameData"],"entity":{"assignData":"assignValue","changeData":100,"emptyData":{"defaultValue":0},"notChangeData":200,"notSameData":2999,"sameData":1}}
7. primary的運用
    /**
     * primary的運用
     * CommonTypeChange是封裝了常用方法的工具接口营搅,大家可以酌情使用,通過primary = true 就能提高優(yōu)先度梆砸,保證子接口的方法被優(yōu)先調(diào)用
     */
@ConverterMapper
public interface TestCommonTypeChangeConverter extends BeanConverter<TestCommonTypeChangeConverter.EntityA, TestCommonTypeChangeConverter.EntityB>,
    CommonTypeChange {

    @Data
    class EntityA {

        private String changeData1;

        private String changeDate2;

    }

    @Data
    class EntityB {

        private long changeData1;

        private int changeDate2;

    }

    @TypeChangeMethod(primary = true)
    default long customMethod(String string) {
        return 999999L;
    }

}
    private void testCommonTypeChange() {
        TestCommonTypeChangeConverter.EntityA entityA = new TestCommonTypeChangeConverter.EntityA();
        entityA.setChangeData1("10000");
        entityA.setChangeDate2("10086");
        TestCommonTypeChangeConverter converter = ConverterHolder.getConverter(TestCommonTypeChangeConverter.class);
        System.out.println("testBeanMethodConverter---------------- 舊對象A:" + JSON.toJSONString(entityA));
        TestCommonTypeChangeConverter.EntityB entityB = converter.convertTo(entityA);
        System.out.println("testBeanMethodConverter ---------------- 新對象B:" + JSON.toJSONString(entityB));
        System.out.println("");
    }

結果

testBeanMethodConverter---------------- 舊對象A:{"changeData1":"10000","changeDate2":"10086"}
testBeanMethodConverter ---------------- 新對象B:{"changeData1":999999,"changeDate2":10086}

5. 總結

  1. 首先 lp-converter-processor 使用很方便转质,就和你用lombok一樣
  2. lp-converter-processor 參數(shù)的識別必須有get/set方法,所以配合lombok使用效果更佳帖世, 比如上述中@Data就是lombok的注解
  3. 其次 lp-converter-processor 很安全休蟹,生成的字節(jié)碼你都能看得見


    image.png

    如果你不滿意或者生成的字節(jié)碼有問題,你還可以自己實現(xiàn)接口日矫,然后調(diào)用ConverterHolder.registerConverter()方法覆蓋字節(jié)碼生成類

/**
 * Converter持有者赂弓,Converter都會注冊到這里
 */
public class ConverterHolder {

    public static final String BEAN_SUFFIX = "_ConverterImpl";

    private static Map<Class<?>, Converter> converterMap = new HashMap<>();

    public static void registerConverter(Class<?> clazz, Converter converter, boolean replace) {
        if(converter != null) {
            Converter oldConverter = converterMap.get(clazz);
            if(oldConverter == null || replace) {
                converterMap.put(clazz, converter);
            }
        }
    }

    public static void registerConverter(Class<?> clazz, Converter converter) {
        registerConverter(clazz, converter, false);
    }

    public static <B extends Converter> B getConverter(Class<B> clazz) {
        try {
            Class.forName(clazz.getName() + BEAN_SUFFIX);
        } catch (ClassNotFoundException e) {

        }
        return  (B)converterMap.get(clazz);
    }

}
  1. 自動生成的工具普遍的風險就是沒法把控自動生成是否按計劃生成,lp-converter-processor 提供字段生成的監(jiān)控 @CheckFieldSetting哪轿, 對應方法(mergeTo/convertTo)上的@CheckFieldSetting覆蓋BeanConverter上@CheckFieldSetting
@ConverterMapper
@CheckFieldSetting
public interface TestBeanConverter extends BeanConverter<TestBeanConverter.EntityA, TestBeanConverter.EntityB> {

    @Data
    class EntityA {

        private HashMap<String, Integer> mapData;

        private int age;

    }

    @Data
    class EntityB {

        private Map<String, Integer> mapData;

        private long age;

    }

    @Override
    @CheckFieldSetting(fieldNameFilter = @FieldNameFilter(assignIgnoreFieldName = "age"))
    MergeResult<EntityB> mergeTo(EntityA source, EntityB target);

    @Override
    @CheckFieldSetting(fieldNameFilter = @FieldNameFilter(assignIgnoreFieldName = "age"))
    EntityB convertTo(EntityA source);

}

上面的如果放開mergeTo的監(jiān)控

    @Override
    MergeResult<EntityB> mergeTo(EntityA source, EntityB target);

編譯就會報錯

java: com.cn.lp.converter.exception.ProcessorException: 生成class異常, 錯誤信息:com.cn.lp.converter.exception.ProcessorException: generate mergeTo fail: 
source Entity not has age field GetMethod type is long

    at com.cn.lp.converter.generator.MergeMethodBuilder.createMethod(MergeMethodBuilder.java:207)

結語

起初只是為了解決一個場景去翻了下MapStruct源碼盈魁,然后發(fā)現(xiàn)這種編寫字節(jié)碼很有趣(MapStruct滿足不了需求),所以就寫了這么一個插件窃诉,這個插件在21年就寫完了杨耙,21年到22年間不斷修修補補,直到現(xiàn)在個人已經(jīng)覺得沒什么迭代空間飘痛,就決定分享大家使用珊膜,最后分享個bean-converter-demo項目,方便大家上手使用宣脉,如果大家使用上有什么問題或者建議歡迎留言
項目地址: https://gitee.com/wqrzsy/lp-demo/tree/master/bean-converter-demo

如果這篇文章對你有幫助請給個star


image.png
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末车柠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子塑猖,更是在濱河造成了極大的恐慌堪遂,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萌庆,死亡現(xiàn)場離奇詭異,居然都是意外死亡币旧,警方通過查閱死者的電腦和手機践险,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巍虫,你說我怎么就攤上這事彭则。” “怎么了占遥?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵俯抖,是天一觀的道長。 經(jīng)常有香客問我瓦胎,道長芬萍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任搔啊,我火速辦了婚禮柬祠,結果婚禮上,老公的妹妹穿的比我還像新娘负芋。我一直安慰自己漫蛔,他們只是感情好,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布旧蛾。 她就那樣靜靜地躺著莽龟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锨天。 梳的紋絲不亂的頭發(fā)上毯盈,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音绍绘,去河邊找鬼奶镶。 笑死,一個胖子當著我的面吹牛陪拘,可吹牛的內(nèi)容都是我干的厂镇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼左刽,長吁一口氣:“原來是場噩夢啊……” “哼捺信!你這毒婦竟也來了?” 一聲冷哼從身側響起欠痴,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤迄靠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后喇辽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掌挚,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年菩咨,在試婚紗的時候發(fā)現(xiàn)自己被綠了吠式。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陡厘。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖特占,靈堂內(nèi)的尸體忽然破棺而出糙置,到底是詐尸還是另有隱情,我是刑警寧澤是目,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布谤饭,位于F島的核電站,受9級特大地震影響懊纳,放射性物質(zhì)發(fā)生泄漏揉抵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一长踊、第九天 我趴在偏房一處隱蔽的房頂上張望功舀。 院中可真熱鬧,春花似錦身弊、人聲如沸辟汰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帖汞。三九已至,卻和暖如春凑术,著一層夾襖步出監(jiān)牢的瞬間翩蘸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工淮逊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留催首,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓泄鹏,卻偏偏與公主長得像郎任,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子备籽,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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