腦洞的由來
開發(fā)過程中經(jīng)常遇到
- 把EntityA的屬性賦值給新的EntityB
- 把Entity的屬性轉換成Map結構
- 把Map結構的數(shù)據(jù)轉換成Entity
當前常見解決方案
- 把Entity轉換成json,再把json轉換成目標Entity
- 第三方工具MapStruct
- spring的BeanUtils
效率對比
- json轉換和BeanUtils都是基于Class的反射映射琴锭,效率不高
- MapStruct是根據(jù)注解生成字節(jié)碼钱反,效率高畔况,但是不夠靈活丈氓,而且沒法滿足場景二和場景三
介紹 lp-converter-processor
- 基于注解生成字節(jié)碼局冰,效率高蔚润。
- 基于jdk8的接口default機制實現(xiàn)靈活編入
- 有字段檢查機制,提供安全檢查能力
- 能兼容lombok
- 國人編寫(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)
- 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結構
- 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. 總結
- 首先 lp-converter-processor 使用很方便转质,就和你用lombok一樣
- lp-converter-processor 參數(shù)的識別必須有get/set方法,所以配合lombok使用效果更佳帖世, 比如上述中@Data就是lombok的注解
-
其次 lp-converter-processor 很安全休蟹,生成的字節(jié)碼你都能看得見
如果你不滿意或者生成的字節(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);
}
}
- 自動生成的工具普遍的風險就是沒法把控自動生成是否按計劃生成,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