Bull(Bean Utils Light Library)是一個將數據從一個對象遞歸地複製到另一個對象的Java-bean到Javabean轉換器。它是通用的,靈活的,可重用的,可配置的,而且非常快。
它是唯一能夠在沒有任何自定義配置的情況下轉換可變、不可變和混合bean的庫。
本文介紹了如何使用它,並給出了每個可用特性的具體示例。
1.依賴性
<dependency>
<groupId>com.hotels.beans</groupId>
<artifactId>bull-bean-transformer</artifactId>
<version>1.7.1</version>
</dependency>
該項目提供兩個不同的構建。,與jdk 8(或以上)jdk 11或者更高。
庫的最新版本可以從自述檔案或來自變化量g(萬一你需要一個jdk 8-兼容版本請參閱Changelog-JDK 8).
2.特徵
本文中解釋的宏特性如下:
bean變換
bean驗證
3.bean變換
bean轉換由Transformer對象,該對象可以執行以下指令獲得:
BeanTransformer transformer = new BeanUtils().getTransformer();
一旦我們有了BeanTransformer對象實例,我們可以使用該方法transform把我們的對象複製到另一個。
使用的方法是:K transform(T sourceObj, Class<K> targetObject);其中,第一個參數表示原對象,第二個參數表示目標類。
例如,給定源和目標類:
public class FromBean { public class ToBean {
private final String name; public BigInteger id;
private final BigInteger id; private final String name;
private final List<FromSubBean> subBeanList; private final List<String> list;
private List<String> list; private final List<ImmutableToSubFoo> nestedObjectList;
private final FromSubBean subObject; private ImmutableToSubFoo nestedObject;
// all args constructor // constructors
// getters and setters... // getters and setters
} }
轉換可以通過以下代碼行獲得:
ToBean toBean = new BeanUtils().getTransformer().transform(fromBean, ToBean.class);
請注意,欄位順序與此無關。
不同欄位名副本
給定兩個欄位數相同但名稱不同的類:
private final String name; private final String differentName;
private final int id; private final int id;
private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;
private final List<String> list; private final List<String> list;
private final FromSubBean subObject; private final ToSubBean subObject;
// all constructors // all args constructor
// getters... // getters...
} }
我們需要定義適當的欄位映射並將其傳遞給Transformer目的:
// the first parameter is the field name in the source object
// the second one is the the field name in the destination one
FieldMapping fieldMapping = new FieldMapping("name", "differentName");
Tansformer transformer = new BeanUtils().getTransformer().withFieldMapping(fieldMapping);
然後,我們可以執行轉換:
ToBean toBean = transformer.transform(fromBean, ToBean.class);
源對象和目標對象之間的映射欄位
案例1:必須從源對象中的嵌套類中檢索目標欄位值
假設對象FromSubBean聲明如下:
public class FromSubBean {
private String serialNumber;
private Date creationDate;
// getters and setters...
}
我們的源類和目標類描述如下:
private final String name; private final String name;
private final FromSubBean subObject; private final String serialNumber;
private final Date creationDate;
/ all args constructor // all args constructor
// getters... // getters...
.而欄位的值serialNumber和creationDate進入ToBean對象需要從subObject,可以定義分隔的屬性點的整個路徑:
FieldMapping serialNumberMapping = new FieldMapping("subObject.serialNumber", "serialNumber");
FieldMapping creationDateMapping = new FieldMapping("subObject.creationDate", "creationDate");
ToBean toBean = new BeanUtils().getTransformer()
.withFieldMapping(serialNumberMapping, creationDateMapping)
.transform(fromBean, ToBean.class);
案例2:必須從源類根檢索目標欄位值(在嵌套類中)
前面的示例突出顯示了如何從源對象中獲取值;而這個示例則解釋了如何將值放入嵌套對象中。
給予:
public class FromBean { public class ToBean {
private final String name; private final String name;
private final FromSubBean nestedObject; private final ToSubBean nestedObject;
private final int x;
// all args constructor // all args constructor
// getters... // getters...
} }
以及:
public class ToSubBean {
private final int x;
// all args constructor
} // getters...
假設x應該映射到欄位:x載於ToSubBean對象,必須將欄位映射定義為:
FieldMapping fieldMapping = new FieldMapping("x", "nestedObject.x");
然後,我們只需要把它傳遞給Transformer並執行轉換:
ToBean toBean = new BeanUtils().getTransformer()
.withFieldMapping(fieldMapping)
.transform(fromBean, ToBean.class);
定義構造器args的不同欄位名
還可以通過添加@ConstructorArg構造函數參數旁邊的注釋。
這個@ConstructorArg接受源對象中對應欄位的名稱作為輸入。
public class FromBean { public class ToBean {
private final String name; private final String differentName;
private final int id; private final int id;
private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;
private final List<String> list; private final List<String> list;
private final FromSubBean subObject; private final ToSubBean subObject;
// all args constructor
// getters...
public ToBean(@ConstructorArg("name") final String differentName,
@ConstructorArg("id") final int id,
} @ConstructorArg("subBeanList") final List<ToSubBean> subBeanList,
@ConstructorArg(fieldName ="list") final List<String> list,
@ConstructorArg("subObject") final ToSubBean subObject) {
this.differentName = differentName;
this.id = id;
this.subBeanList = subBeanList;
this.list = list;
this.subObject = subObject;
}
// getters...
}
然後:
ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);
對特定欄位Lambda函數應用自定義轉換
我們知道,在現實生活中,很少需要在兩個Javabean之間複製幾乎相同的信息,經常會發生這樣的情況:
目標對象的結構與源對象完全不同。
在複製特定欄位值之前,我們需要對它執行一些操作。
必須驗證目標對象的欄位
目標對象有一個比源對象更多的欄位,需要填充來自不同源的內容。
公牛給出了在特定領域執行任何一種操作的可能性,實際上是利用了Lambda表達式,開發人員可以定義自己的方法,在複製a值之前應用該方法。
讓我們用一個例子更好地解釋它:
鑑於以下情況Source班級:
public class FromFoo {
private final String id;
private final String val;
private final List<FromSubFoo> nestedObjectList;
// all args constructor
// getters
}
以及以下內容Destination班級:
public class MixedToFoo {
public String id;
@NotNull
private final Double val;
// constructors
// getters and setters
}
假設val在我們的變壓器中,場需要乘以一個隨機值,我們有兩個問題:
這個val欄位的類型與Source對象,確實是String一個是Double
我們需要指導圖書館如何應用數學運算。
這很簡單,您只需定義自己的lambda表達式即可:
FieldTransformer<String, Double> valTransformer =
new FieldTransformer<>("val",
n -> Double.valueOf(n) * Math.random());
表達式將應用於具有名稱的欄位。val在目標對象中。
最後一步是將函數傳遞給Transformer例如:
MixedToFoo mixedToFoo = new BeanUtils().getTransformer()
.withFieldTransformer(valTransformer)
.transform(fromFoo, MixedToFoo.class);
在源對象中缺少欄位的情況下指定默認值
有時,在目標對象比源對象具有更多欄位的情況下會發生這種情況;在本例中,BeanUtils庫將引發異常,通知它它們無法執行映射,因為它們不知道必須從何處檢索值。
典型的情況如下:
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
private String notExistingField; // this will be null and no exceptions will be raised
// constructors... // constructors...
// getters... // getters and setters...
} }
但是,我們可以將庫配置為為欄位類型分配默認值(如:0為int類型,null為String等等)
ToBean toBean = new BeanUtils().getTransformer()
.setDefaultValueForMissingField(true)
.transform(fromBean, ToBean.class);
在源對象中缺少欄位的情況下應用轉換函數
下面的示例演示如何在源對象中不存在的欄位上分配默認值(或lambda函數的結果):
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
private String notExistingField; // this will have value: sampleVal
// all args constructor // constructors...
// getters... // getters and setters...
} }
我們需要做的是分配一個FieldTransformer函數到特定欄位:
FieldTransformer<String, String> notExistingFieldTransformer =
new FieldTransformer<>("notExistingField", () -> "sampleVal");
上述函數將為該欄位指定一個固定值。notExistingField,但是我們可以返回任何內容,例如,我們可以調用一個外部方法,該方法返回一組操作後獲得的值,如下所示:
FieldTransformer<String, String> notExistingFieldTransformer =
new FieldTransformer<>("notExistingField", () -> calculateValue());
但是,最後,我們只需要將它傳遞給Transformer.
ToBean toBean = new BeanUtils().getTransformer()
.withFieldTransformer(notExistingFieldTransformer)
.transform(fromBean, ToBean.class);
將轉換函數應用於嵌套對象中的特定欄位
案例1:應用於嵌套類中特定欄位的Lambda轉換函數
給予:
public class FromBean { public class ToBean {
private final String name; private final String name;
private final FromSubBean nestedObject; private final ToSubBean nestedObject;
// all args constructor // all args constructor
// getters... // getters...
} }
以及:
public class FromSubBean { public class ToSubBean {
private final String name; private final String name;
private final long index; private final long index;
// all args constructor // all args constructor
// getters... // getters...
} }
假設lambda轉換函數只應用於欄位name載於ToSubBean對象時,必須將轉換函數定義為:
FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("nestedObject.name", StringUtils::capitalize);
然後,將函數傳遞給Transformer目的:
ToBean toBean = new BeanUtils().getTransformer()
.withFieldTransformer(nameTransformer)
.transform(fromBean, ToBean.class);
案例2:應用於特定欄位的Lambda轉換函數與其位置無關
想像一下在我們Destination類中,有更多相同名稱的欄位出現在不同的類中,並且我們希望對所有這些類應用相同的轉換函數;有一個允許這樣做的設置。
以上面的對象為例,並假設我們希望利用name欄位獨立於它們的位置,我們可以這樣做:
FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("name", StringUtils::capitalize);
然後:
ToBean toBean = beanUtils.getTransformer()
.setFlatFieldTransformation(true)
.withFieldTransformer(nameTransformer)
.transform(fromBean, ToBean.class);
靜態變壓器功能:
BeanUtils 提供了轉換器方法的「靜態」版本,當需要在複合lambda表達式中應用時,它可以是一個附加值。
例如:
List<FromFooSimple> fromFooSimpleList = Arrays.asList(fromFooSimple, fromFooSimple);
這一轉變應該由以下幾個方面來完成:
BeanTransformer transformer = new BeanUtils().getTransformer();
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(fromFoo -> transformer.transform(fromFoo, ImmutableToFooSimple.class))
.collect(Collectors.toList());
由於這個特性,可以為特定的對象類創建特定的轉換器函數:
Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(ImmutableToFooSimple.class);
然後,可以將列錶轉換為:
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(transformerFunction)
.collect(Collectors.toList());
但是,可能發生的情況是,我們已經配置了一個BeanTransformer具有多個欄位、映射和轉換函數的實例,我們也希望在此轉換中使用它,因此我們需要做的是從我們的轉換器創建轉換器函數:
BeanTransformer transformer = new BeanUtils().getTransformer()
.withFieldMapping(new FieldMapping("a", "b"))
.withFieldMapping(new FieldMapping("c", "d"))
.withTransformerFunction(new FieldTransformer<>("locale", Locale::forLanguageTag));
Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(transformer, ImmutableToFooSimple.class);
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
.map(transformerFunction)
.collect(Collectors.toList());
啟用Java Bean驗證
庫提供的特性之一是bean驗證。它包括檢查轉換後的對象是否滿足在其上定義的約束。驗證在兩種默認情況下都有效。javax.constraints還有定製的。
假設欄位id在FromBean實例是null.
public class FromBean { public class ToBean {
private final String name; @NotNull
private final BigInteger id; public BigInteger id;
private final String name;
// all args constructor // all args constructor
// getters... // getters and setters...
} }
添加以下配置後,將在轉換過程結束時執行驗證,在我們的示例中,將拋出一個異常,通知對象無效:
ToBean toBean = new BeanUtils().getTransformer()
.setValidationEnabled(true)
.transform(fromBean, ToBean.class);
在現有實例上複製
即使庫能夠創建給定類的新實例,並用給定對象中的值填充它,也可能需要在已有實例上注入值,因此給出以下Java bean:
public class FromBean { public class ToBean {
private final String name; private String name;
private final FromSubBean nestedObject; private ToSubBean nestedObject;
// all args constructor // constructor
// getters... // getters and setters...
} }
如果我們需要對已經存在的對象執行副本,則只需將類實例傳遞給transform職能:
ToBean toBean = new ToBean();
new BeanUtils().getTransformer().transform(fromBean, toBean);
給定欄位集上的跳躍變換
如果我們將源對象值複製到已經存在的實例中(某些值已經設置),則可能需要避免轉換操作覆蓋現有值。下面的示例說明了如何做到這一點:
public class FromBean { public class ToBean {
private final String name; private String name;
private final FromSubBean nestedObject; private ToSubBean nestedObject;
// all args constructor // constructor
// getters... // getters and setters...
} }
public class FromBean2 {
private final int index;
private final FromSubBean nestedObject;
// all args constructor
// getters...
}
如果我們需要跳過一組欄位的轉換,只需將它們的名稱傳遞給skipTransformationForField方法。例如,如果我們想跳過欄位上的轉換nestedObject,這就是我們需要做的:
ToBean toBean = new ToBean();
new BeanUtils().getTransformer()
.skipTransformationForField("nestedObject")
.transform(fromBean, toBean);
此功能允許轉換保存來自不同源的數據的對象.
為了更好地解釋這個函數,讓我們假設ToBean(上文定義)應改為:
name欄位值已從FromBean對象
nestedObject欄位值已從FromBean2對象
可以通過這樣做來實現目標:
// create the destination object
ToBean toBean = new ToBean();
// execute the first transformation skipping the copy of: 'nestedObject' field that should come from the other source object
new BeanUtils().getTransformer()
.skipTransformationForField("nestedObject")
.transform(fromBean, toBean);
// then execute the transformation skipping the copy of: 'name' field that should come from the other source object
new BeanUtils().getTransformer()
.skipTransformationForField("name")
.transform(fromBean2, toBean);
欄位類型轉換
在欄位類型與源類和目標不同的情況下,我們有以下示例:
public class FromBean {
public class ToBean {
private final String index;
private int index;
// all args constructor
// constructor
// getters...
// getters and setters...
}
}
它可以使用特定的轉換函數進行轉換:
FieldTransformer<String, Integer> indexTransformer = new FieldTransformer<>("index", Integer::parseInt);
ToBean toBean = new BeanUtils()
.withFieldTransformer(indexTransformer)
.transform(fromBean, ToBean.class);
用Builder模式實現Java Bean的轉換
庫使用不同類型的Builder模式支持JavaBean的轉換:標準模式(默認支持)和自定義模式。讓我們詳細了解它們,以及如何啟用自定義Builder類型轉換。
讓我們從標準一默認支持:
public class ToBean {
private final Class<?> objectClass;
private final Class<?> genericClass;
ToBean(final Class<?> objectClass, final Class<?> genericClass) {
this.objectClass = objectClass;
this.genericClass = genericClass;
}
public static ToBeanBuilder builder() {
return new ToBean.ToBeanBuilder();
}
// getter methods
public static class ToBeanBuilder {
private Class<?> objectClass;
private Class<?> genericClass;
ToBeanBuilder() {
}
public ToBeanBuilder objectClass(final Class<?> objectClass) {
this.objectClass = objectClass;
return this;
}
public ToBeanBuilder genericClass(final Class<?> genericClass) {
this.genericClass = genericClass;
return this;
}
public com.hotels.transformer.model.ToBean build() {
return new ToBean(this.objectClass, this.genericClass);
}
}
}
如前所述,這不需要額外的設置,因此可以通過執行以下操作來執行轉換:
ToBean toBean = new BeanTransformer()
.transform(sourceObject, ToBean.class);
自定義生成器模式:
public class ToBean {
private final Class<?> objectClass;
private final Class<?> genericClass;
ToBean(final ToBeanBuilder builder) {
this.objectClass = builder.objectClass;
this.genericClass = builder.genericClass;
}
public static ToBeanBuilder builder() {
return new ToBean.ToBeanBuilder();
}
// getter methods
public static class ToBeanBuilder {
private Class<?> objectClass;
private Class<?> genericClass;
ToBeanBuilder() {
}
public ToBeanBuilder objectClass(final Class<?> objectClass) {
this.objectClass = objectClass;
return this;
}
public ToBeanBuilder genericClass(final Class<?> genericClass) {
this.genericClass = genericClass;
return this;
}
public com.hotels.transformer.model.ToBean build() {
return new ToBean(this);
}
}
}
要轉換上面的Bean,要使用的指令是:
ToBean toBean = new BeanTransformer()
.setCustomBuilderTransformationEnabled(true)
.transform(sourceObject, ToBean.class);
4.bean驗證
對一組規則的類驗證是非常寶貴的,特別是當我們需要確保對象數據符合我們的期望時。
「欄位驗證」方面是Bull提供的特性之一,它是完全自動的--您只需要用一個現有的javax.validation.約束(或定義一個自定義的約束)對您的欄位進行注釋,然後對其執行驗證。
給定以下bean:
public class SampleBean {
@NotNull
private BigInteger id;
private String name;
// constructor
// getters and setters...
}
上述對象的實例:
SampleBean sampleBean = new SampleBean();
和一行代碼,例如:
new BeanUtils().getValidator().validate(sampleBean);
這將拋出一個InvalidBeanException,作為田野id是null.
結語
我試著用例子來解釋如何使用Bull項目提供的主要功能。然而,查看完整的原始碼可能更有幫助。
可以找到更多的示例,查看在Bull項目上實現的測試用例。這裡.
GitHub還包含一個示例彈簧靴項目,該項目使用庫在不同層之間轉換請求/響應對象,可以找到這裡.
為感謝您對我們的認可,特意準備了一些IT入門和進階的乾貨
包括:Java、UI設計、H5前端、Python+人工智慧、軟體測試和新媒體運營六大學科視頻資料。以及IT就業大禮包。
線上視頻、音頻,隨時學習觀看
關注我們並私信「資料」即可獲取。