主題
最早以前自學java web的時候,數據庫查詢出來一個Entity對象(CMP對象).就直接傳給前台展示了.並沒有用到DTO對象,開始並沒有覺得有什么不好...后來發現還是需要一些DTO對象來專門用來傳值與前台展示用的.因為直接使用Entity對象有幾個地方會比較麻煩:
1.Entity對象的成員域和數據庫字段是對應的(比如使用hibernate).所以不能額外向里面增加字段.但是有時候前台就是需要這個字段.反之,前台要向后台傳一些值,entity也沒辦法接受,前台Form表單不可能和數據庫公用同一套對象的.
2.有時候完成1個業務需要N個entity.一個一個傳給前台很麻煩.這個不是最關鍵的,最關鍵的是如果你的service第一次需求定下來的時候只需要1個entity對象做為參數,后來需求變更了,需要用到另外一個entity,這個時候如果修改service的方法簽名,增加一個入參,那所有用到這個service的地方都需要修改.而且其他地方不一定有那個業務相關的entity.而方法參數為單獨一個DTO的時候就沒有這個問題,需要什么其他屬性,直接在DTO增加成員域即可.
3.entity可能是由框架控制生命周期的,比如hibernate,如果他的某個屬性是懶加載的,那萬一不在session中調用的話會拋出異常,而DTO對象很干凈,並不會有什么問題.你可以自己控制DTO,喜歡就存在緩存里,不喜歡也沒關系.同時entity對象如果屬性變化的話會有是否需要同步更新數據庫的問題,而DTO與數據庫並沒有什么卵關系.
對象拷貝
所以DTO還是需要的.這樣的話就需要entity與dto之間進行數據庫的拷貝. 最簡單的方法當然是自己調用setget方法..但是顯然這樣比較麻煩.....因為成員域多的話一個一個set起來很累....
有不少框架都支持對象間屬性的拷貝.公司的方法是采用CGLIB的BeanCopier去copy對象,並且在外面稍微包裝了一層.
1 @SuppressWarnings("rawtypes") 2 public final class ConverterUtil { 3 4 /** 5 * The Constant LOGGER. 6 */ 7 private static final Logger LOGGER = LoggerFactory.getLogger(ConverterUtil.class); 8 9 /** 10 * The Constant CACHED_COPIER_MAP. 11 */ 12 private static final Map<String, BeanCopier> CACHED_COPIER_MAP = new ConcurrentHashMap<String, BeanCopier>(); 13 14 /** 15 * The Constant CACHED_CUSTOM_CONVERTER_MAP. 16 */ 17 private static final Map<String, ObjectConverter> CACHED_CUSTOM_CONVERTER_MAP = new ConcurrentHashMap<String, ObjectConverter>(); 18 19 /** 20 * Instantiates a new converter util. 21 */ 22 private ConverterUtil() { 23 } 24 25 /** 26 * Convert list. 27 * 28 * @param <T> 29 * the generic type 30 * @param <F> 31 * the generic type 32 * @param source 33 * the source 34 * @param target 35 * the target 36 * @param sourceList 37 * the source list 38 * @return the list 39 */ 40 public static <T, F> List<F> convertList(Class<T> source, Class<F> target, List<T> sourceList) { 41 return convertList(source, target, sourceList, null, null); 42 } 43 44 /** 45 * Convert list. 46 * 47 * @param <T> 48 * the generic type 49 * @param <F> 50 * the generic type 51 * @param source 52 * the source 53 * @param target 54 * the target 55 * @param sourceList 56 * the source list 57 * @param converter 58 * the converter 59 * @param customConverterClass 60 * the custom converter class 61 * @return the list 62 */ 63 public static <T, F> List<F> convertList(Class<T> source, Class<F> target, List<T> sourceList, Converter converter,// NOSONAR 64 Class<? extends ObjectConverter> customConverterClass) { 65 if (CollectionUtils.isNotEmpty(sourceList)) { 66 @SuppressWarnings("unchecked") 67 List<F> targetList = new ArrayList(); 68 for (T t : sourceList) { 69 try { 70 F f = target.newInstance(); 71 targetList.add(convert(t, f, converter, customConverterClass)); 72 } catch (Exception e) { 73 LOGGER.error("When copy instance" + t, e); 74 } 75 } 76 return targetList; 77 } else { 78 return null;// NOSONAR 79 } 80 81 } 82 83 /** 84 * Convert. 85 * 86 * @param <T> 87 * the generic type 88 * @param <F> 89 * the generic type 90 * @param source 91 * the source 92 * @param target 93 * the target 94 * @return the f 95 */ 96 public static <T, F> F convert(T source, F target) { 97 return convert(source, target, null, null); 98 } 99 100 /** 101 * Convert. 102 * 103 * @param <T> 104 * the generic type 105 * @param <F> 106 * the generic type 107 * @param source 108 * the source 109 * @param target 110 * the target 111 * @param converter 112 * the converter 113 * @param customConverterClass 114 * the custom converter class 115 * @return the f 116 */ 117 public static <T, F> F convert(T source, F target, Converter converter, 118 Class<? extends ObjectConverter> customConverterClass) { 119 if (source == null || target == null) { 120 return null; // NOSONAR 121 } 122 copy(source, target, converter, customConverterClass); 123 return target; 124 } 125 126 /** 127 * Private methods. 128 * 129 * @param <T> 130 * the generic type 131 * @param <F> 132 * the generic type 133 * @param source 134 * the source 135 * @param target 136 * the target 137 * @param converter 138 * the converter 139 * @param customConverterClass 140 * the custom converter class 141 */ 142 143 @SuppressWarnings("unchecked") 144 private static <T, F> void copy(T source, F target, Converter converter, 145 Class<? extends ObjectConverter> customConverterClass) { 146 BeanCopier beanCopier = getBeanCopierInstance(source, target.getClass(), converter); 147 beanCopier.copy(source, target, converter); 148 ObjectConverter customConverter = getCustomConverterInstance(customConverterClass); 149 if (customConverter != null) { 150 if (target.getClass().getName().endsWith("CMP")) { 151 customConverter.convertFromDto(source, target); 152 } else if (target.getClass().getName().endsWith("DTO")) { 153 customConverter.convertToDto(source, target); 154 } 155 } 156 } 157 158 /** 159 * Gets the bean copier instance. 160 * 161 * @param <T> 162 * the generic type 163 * @param <F> 164 * the generic type 165 * @param source 166 * the source 167 * @param targetClass 168 * the target class 169 * @param converter 170 * the converter 171 * @return the bean copier instance 172 */ 173 private static <T, F> BeanCopier getBeanCopierInstance(T source, Class<F> targetClass, Converter converter) { 174 String key = source.getClass().getName() + "#" + targetClass.getName(); 175 BeanCopier beanCopier = CACHED_COPIER_MAP.get(key); 176 if (beanCopier == null) { 177 synchronized (CACHED_COPIER_MAP) { 178 beanCopier = CACHED_COPIER_MAP.get(key); 179 if (beanCopier == null) { 180 beanCopier = TypeAwareBeanCopier.instantiate(source.getClass(), targetClass, converter != null); 181 CACHED_COPIER_MAP.put(key, beanCopier); 182 } 183 } 184 } 185 return beanCopier; 186 } 187 188 /** 189 * Gets the custom converter instance. 190 * 191 * @param <T> 192 * the generic type 193 * @param <F> 194 * the generic type 195 * @param customConverterClass 196 * the custom converter class 197 * @return the custom converter instance 198 */ 199 private static <T, F> ObjectConverter getCustomConverterInstance( 200 Class<? extends ObjectConverter> customConverterClass) { 201 if (customConverterClass == null) { 202 return null;// NOSONAR 203 } 204 String key = customConverterClass.getName(); 205 ObjectConverter converter = CACHED_CUSTOM_CONVERTER_MAP.get(key); 206 if (converter == null) { 207 synchronized (CACHED_CUSTOM_CONVERTER_MAP) { 208 try { 209 converter = (ObjectConverter) SpringContextUtil.getBean(customConverterClass); 210 } catch (BeansException e) {// NOSONAR 211 LOGGER.info(customConverterClass.getName() + " is not a component, need new instance.");// NOSONAR 212 } 213 if (converter == null) { 214 try { 215 converter = customConverterClass.newInstance(); 216 CACHED_CUSTOM_CONVERTER_MAP.put(key, converter); 217 } catch (InstantiationException e) { 218 LOGGER.info(e.getMessage(), e); 219 return null; 220 } catch (IllegalAccessException e) { 221 LOGGER.info(e.getMessage(), e); 222 return null; 223 } 224 } 225 } 226 } 227 return converter; 228 } 229 230 }
完整的代碼就這么多...
convert一個list,其實就是convert了N個普通對象.
convert 1個對象,其實就是new了一個相同類型的對象,然后copy成員域.
所以核心還是copy方法
1 private static <T, F> void copy(T source, F target, Converter converter, 2 Class<? extends ObjectConverter> customConverterClass) { 3 BeanCopier beanCopier = getBeanCopierInstance(source, target.getClass(), converter); 4 beanCopier.copy(source, target, converter); 5 ObjectConverter customConverter = getCustomConverterInstance(customConverterClass); 6 if (customConverter != null) { 7 if (target.getClass().getName().endsWith("CMP")) { 8 customConverter.convertFromDto(source, target); 9 } else if (target.getClass().getName().endsWith("DTO")) { 10 customConverter.convertToDto(source, target); 11 } 12 } 13 }
copy方法就2個步驟,第一個步驟是調用CGLIB的BeanCopier的copy方法去copy對象,然后再調用自己寫的ObjectConverter接口的實現類去做額外的copy..ObjectConverter接口有2個方法,一個是convertFromDto,就是DTO -> Entity的拷貝,另外一個是convertToDto就是Entity -> Dto或者Dto -> Dto的拷貝.
小小的總結
公司的對象間拷貝方法還是比較簡單的,同時也蠻好用的,畢竟大多數時候就是收集下前台表單的數據,然后Copy到要保存的entity中,或者數據庫查出來Entity,拷貝到前台去展示.
一般String呀,int呀這些都能很方便的拷貝,不過如果成員是List呀,引用對象呀什么的....那就只能自己去實現ObjectConverter接口做額外copy 或者為每個引用對象再掉一次copy方法.
這點是算個小缺陷吧....不過大多數情況下這個簡易的轉化工具還是超級好用的.
