在做業務的時候,我們有時為了隔離變化,會将DAO查詢出來的Entity,和對外提供的DTO隔離開來。大概90%的時候,它們的結構都是類似的,但是我們很不喜歡寫很多冗長的b.setF1(a.getF1())這樣的代碼,于是我們需要BeanCopier來幫助我們。
BeanCopier
其實已經有很多開源版本,例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils甚至是Cglib都提供了這樣的功能。下面介紹Cglib的BeanCopier的使用。
1、通過拷貝bean對象來測試BeanCopier的基本使用和特性
Java代碼
- public class OrderEntity {
- private int id;
- private String name;
- // Getters and setters are omitted
- }
Java代碼
- public class OrderDto {
- private int id;
- private String name;
- // Getters and setters are omitted
- }
Java代碼
- public class PropWithDiffType {
- private Integer id;
- private String name;
- // Getters and setters are omitted
- }
Java代碼
- public class LackOfSetter {
- private int id;
- private String name;
- public LackOfSetter() {
- }
- public LackOfSetter(int id, String name) {
- this.id = id;
- this.name = name;
- }
- // Getters and setters are omitted
- // public void setName(String name) {
- // this.name = name;
- // }
- }
1. 屬性名稱、類型都相同:
Java代碼
- @Test
- public void normalCopyTest() {
- OrderEntity entity = new OrderEntity();
- entity.setId(1);
- entity.setName("orderName");
- final BeanCopier copier = BeanCopier.create(OrderEntity.class, OrderDto.class, false);
- OrderDto dto = new OrderDto();
- copier.copy(entity, dto, null);
- Assert.assertEquals(1, dto.getId());
- Assert.assertEquals("orderName", dto.getName());
- }
結論:拷貝OK。
2. 屬性名稱相同、類型不同:
Java代碼
- @Test
- public void sameNameDifferentTypeCopyTest() {
- OrderEntity entity = new OrderEntity();
- entity.setId(1);
- entity.setName("orderName");
- final BeanCopier copier = BeanCopier.create(OrderEntity.class, PropWithDiffType.class, false);
- PropWithDiffType dto = new PropWithDiffType();
- copier.copy(entity, dto, null);
- Assert.assertEquals(null, dto.getId()); // OrderEntity的id為int類型,而PropWithDiffType的id為Integer類型,不拷貝
- Assert.assertEquals("orderName", dto.getName());
- }
結論:名稱相同而類型不同的屬性不會被拷貝。
注意:即使源類型是 原始類型(int, short和char等),目标類型是其 包裝類型(Integer, Short和Character等),或反之:都 不會被拷貝。
3. 源類和目标類有相同的屬性(兩者的getter都存在),但目标類的setter不存在
Java代碼
- @Test
- public void targetLackOfSetterCopyTest() {
- OrderEntity entity = new OrderEntity();
- entity.setId(1);
- entity.setName("orderName");
- final BeanCopier copier = BeanCopier.create(OrderEntity.class, LackOfSetter.class, false); // 抛NullPointerException
- LackOfSetter dto = new LackOfSetter();
- copier.copy(entity, dto, null);
- }
結論:建立BeanCopier的時候抛異常。
導緻異常的原因是BeanCopier類的第128~133行
Java代碼
- for (int i = 0; i < setters.length; i++) { // 周遊目标類的屬性描述集
- PropertyDescriptor setter = setters[i];
- PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); // 從源類擷取和目标類屬性名稱相同的屬性描述
- if (getter != null) {
- MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); // 擷取源類屬性的getter方法
- MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); // 擷取目标類屬性的setter方法。LackOfSetter類name屬性的setter方法沒有,是以報錯
4. 源類或目标類的setter比getter少
Java代碼
- @Test
- public void sourceLackOfSetterCopyTest() {
- LackOfSetter source = new LackOfSetter(1, "throne");
- final BeanCopier copier = BeanCopier.create(LackOfSetter.class, OrderDto.class, false);
- OrderDto dto = new OrderDto();
- copier.copy(source, dto, null);
- Assert.assertEquals(1, dto.getId());
- Assert.assertEquals("throne", dto.getName());
- }
結論:拷貝OK。此時的setter多餘,但不會報錯。
總結:
1. BeanCopier隻拷貝名稱和類型都相同的屬性。
2. 當目标類的setter數目比getter少時,建立BeanCopier會失敗而導緻拷貝不成功。
二、自定義轉換器
當源和目标類的屬性類型不同時,不能拷貝該屬性,此時我們可以通過實作Converter接口來自定義轉換器
源類和目标類:
Java代碼
- public class AccountEntity {
- private int id;
- private Timestamp createTime;
- private BigDecimal balance;
- // Getters and setters are omitted
- }
Java代碼
- public class AccountDto {
- private int id;
- private String name;
- private String createTime;
- private String balance;
- // Getters and setters are omitted
- }
1. 不使用Converter
Java代碼
- public class BeanCopierConverterTest {
- @Test
- public void noConverterTest() {
- AccountEntity po = new AccountEntity();
- po.setId(1);
- po.setCreateTime(new Timestamp(10043143243L));
- po.setBalance(BigDecimal.valueOf(4000L));
- BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, false);
- AccountDto dto = new AccountDto();
- copier.copy(po, dto, null);
- Assert.assertNull(dto.getCreateTime()); // 類型不同,未拷貝
- Assert.assertNull(dto.getBalance()); // 類型不同,未拷貝
- }
- }
2. 使用Converter
基于目标對象的屬性出發,如果源對象有相同名稱的屬性,則調一次convert方法:
Java代碼
- package net.sf.cglib.core;
- public interface Converter {
- // value 源對象屬性,target 目标對象屬性類,context 目标對象setter方法名
- Object convert(Object value, Class target, Object context);
- }
Java代碼
- @Test
- public void converterTest() {
- AccountEntity po = new AccountEntity();
- po.setId(1);
- po.setCreateTime(Timestamp.valueOf("2014-04-12 16:16:15"));
- po.setBalance(BigDecimal.valueOf(4000L));
- BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, true);
- AccountConverter converter = new AccountConverter();
- AccountDto dto = new AccountDto();
- copier.copy(po, dto, converter);
- Assert.assertEquals("2014-04-12 16:16:15", dto.getCreateTime());
- Assert.assertEquals("4000", dto.getBalance());
- }
- static class AccountConverter implements Converter {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- @SuppressWarnings("rawtypes")
- @Override
- public Object convert(Object value, Class target, Object context) {
- if (value instanceof Integer) {
- return (Integer) value;
- } else if (value instanceof Timestamp) {
- Timestamp date = (Timestamp) value;
- return sdf.format(date);
- } else if (value instanceof BigDecimal) {
- BigDecimal bd = (BigDecimal) value;
- return bd.toPlainString();
- }
- return null;
- }
- }
注:一旦使用Converter,BeanCopier隻使用Converter定義的規則去拷貝屬性,是以在convert方法中要考慮所有的屬性。
三、緩存BeanCopier提升性能
BeanCopier拷貝速度快,性能瓶頸出現在建立BeanCopier執行個體的過程中。 是以,把建立過的BeanCopier執行個體放到緩存中,下次可以直接擷取,提升性能:
Java代碼
- public class CachedBeanCopier {
- static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();
- public static void copy(Object srcObj, Object destObj) {
- String key = genKey(srcObj.getClass(), destObj.getClass());
- BeanCopier copier = null;
- if (!BEAN_COPIERS.containsKey(key)) {
- copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
- BEAN_COPIERS.put(key, copier);
- } else {
- copier = BEAN_COPIERS.get(key);
- }
- copier.copy(srcObj, destObj, null);
- }
- private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
- return srcClazz.getName() + destClazz.getName();
- }
- }