Dozer
對(duì)外接口,一般都使用特定的DTO對(duì)象,而不會(huì)使用領(lǐng)域模型,以避免兩者的變動(dòng)互相影響。其他框架適配等情形,也可能需要DTO對(duì)象。
如果手工一個(gè)個(gè)屬性的在兩個(gè)對(duì)象間復(fù)制有點(diǎn)累人,如果對(duì)象里面還有對(duì)象,就更累了。所以希望有一個(gè)工具類,可以一行代碼就把對(duì)象A中的屬性全部Copy到對(duì)象B中。 普通的反射框架不適合做這個(gè)事情,看看Dozer 所支持的特性就知道了:
支持兩個(gè)對(duì)象間的同一屬性的類型是異構(gòu)的對(duì)象,比如CarDTO的engine屬性是EngineDTO, 而Car的engine屬性是Engine。
支持String <-> 基礎(chǔ)類型的轉(zhuǎn)換,比如CarDTO的price屬性是String, 而Car的price屬性是Double.
支持Collection類型間的轉(zhuǎn)換,比如String[] <-> List
支持雙向依賴,比如Product有個(gè)屬性是List parts, 而每個(gè)Part也有一個(gè)Product屬性,此時(shí)Product與Part雙向依賴了。
屬性名實(shí)在不一致時(shí),可以用@Mapping定義,而且只在其中一邊定義就可以了。
但Dozer 也有個(gè)缺點(diǎn),必須基于getter/setter,不能直接訪問public field,卡住了我讓Entity/DTO都取消掉getter/setter的計(jì)劃。
Dozer已比較成熟,所以更新很慢。另一個(gè)類似但更新較勤快的項(xiàng)目叫Orika
In SpringSide
在core中封裝了一個(gè)BeanMapper,實(shí)現(xiàn)如下功能:
持有Dozer單例, 避免重復(fù)創(chuàng)建DozerMapper消耗資源.
自動(dòng)泛型類型轉(zhuǎn)換.
批量轉(zhuǎn)換Collection中的所有對(duì)象.
區(qū)分創(chuàng)建新的B對(duì)象與將對(duì)象A值賦值到已存在的B對(duì)象的函數(shù).
在showcase中有一個(gè)DozerDemo,詳細(xì)演示了Dozer上述的各種特性。
所需jar包
1、<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.3.1</version>
</dependency>
2、<dependency>
<groupId>org.springside</groupId>
<artifactId>springside-core</artifactId>
</dependency>
1 package org.springside.examples.showcase.utilities.dozer;
2
3 import static org.junit.Assert.*;
4
5 import java.util.List;
6
7 import org.dozer.Mapping;
8 import org.junit.Test;
9 import org.springside.modules.mapper.BeanMapper;
10
11 /**
12 * 演示Dozer如何只要屬性名相同,可以罔顧屬性類型是基礎(chǔ)類型<->String的差別,Array轉(zhuǎn)為List,甚至完全另一種DTO的差別。
13 * 并且能完美解決循環(huán)依賴問題。
14 * 使用@Mapping能解決屬性名不匹配的情況.
15 */
16 public class DozerDemo {
17
18 /**
19 * 從一個(gè)ProductDTO實(shí)例,創(chuàng)建出一個(gè)新的Product實(shí)例。
20 */
21 @Test
22 public void map() {
23 ProductDTO productDTO = new ProductDTO();
24 productDTO.setName("car");
25 productDTO.setPrice("200");
26
27 PartDTO partDTO = new PartDTO();
28 partDTO.setName("door");
29 partDTO.setProduct(productDTO);
30
31 productDTO.setParts(new PartDTO[] { partDTO });
32
33 //ProductDTO->Product
34 Product product = BeanMapper.map(productDTO, Product.class);
35
36 assertEquals("car", product.getProductName());
37 //原來的字符串被Map成Double。
38 assertEquals(new Double(200), product.getPrice());
39 //原來的PartDTO同樣被Map成Part ,Array被Map成List
40 assertEquals("door", product.getParts().get(0).getName());
41 //Part中循環(huán)依賴的Product同樣被賦值。
42 assertEquals("car", product.getParts().get(0).getProduct().getProductName());
43
44 //再反向從Product->ProductDTO
45 ProductDTO productDTO2 = BeanMapper.map(product, ProductDTO.class);
46 assertEquals("car", productDTO2.getName());
47 assertEquals("200.0", productDTO2.getPrice());
48 assertEquals("door", productDTO2.getParts()[0].getName());
49 }
50
51 /**
52 * 演示將一個(gè)ProductDTO實(shí)例的內(nèi)容,Copy到另一個(gè)已存在的Product實(shí)例.
53 */
54 @Test
55 public void copy() {
56 ProductDTO productDTO = new ProductDTO();
57 productDTO.setName("car");
58 productDTO.setPrice("200");
59
60 PartDTO partDTO = new PartDTO();
61 partDTO.setName("door");
62 partDTO.setProduct(productDTO);
63
64 productDTO.setParts(new PartDTO[] { partDTO });
65
66 //已存在的Product實(shí)例
67 Product product = new Product();
68 product.setProductName("horse");
69 product.setWeight(new Double(20));
70
71 BeanMapper.copy(productDTO, product);
72
73 //原來的horse,被替換成car
74 assertEquals("car", product.getProductName());
75 //原來的20的屬性被覆蓋成200,同樣被從字符串被專為Double。
76 assertEquals(new Double(200), product.getPrice());
77 //DTO中沒有的屬性值,在Product中被保留
78 assertEquals(new Double(20), product.getWeight());
79 //Part中循環(huán)依賴的Product同樣被賦值。
80 assertEquals("car", product.getParts().get(0).getProduct().getProductName());
81 }
82
83 public static class Product {
84 private String productName;
85 private Double price;
86 private List<Part> parts;
87 //DTO中沒有的屬性
88 private Double weight;
89
90 public String getProductName() {
91 return productName;
92 }
93
94 public void setProductName(String productName) {
95 this.productName = productName;
96 }
97
98 public Double getPrice() {
99 return price;
100 }
101
102 public void setPrice(Double price) {
103 this.price = price;
104 }
105
106 public List<Part> getParts() {
107 return parts;
108 }
109
110 public void setParts(List<Part> parts) {
111 this.parts = parts;
112 }
113
114 public Double getWeight() {
115 return weight;
116 }
117
118 public void setWeight(Double weight) {
119 this.weight = weight;
120 }
121
122 }
123
124 public static class Part {
125 //反向依賴Product
126 private Product product;
127
128 private String name;
129
130 public String getName() {
131 return name;
132 }
133
134 public void setName(String name) {
135 this.name = name;
136 }
137
138 public Product getProduct() {
139 return product;
140 }
141
142 public void setProduct(Product product) {
143 this.product = product;
144 }
145 }
146
147 public static class ProductDTO {
148 //定義到Product中的productName,只要在一邊定義,雙向轉(zhuǎn)換都可以使用.
149 @Mapping("productName")
150 private String name;
151 //類型為String 而非 Double
152 private String price;
153 //類型為Array而非List, PartDTO而非Part
154 private PartDTO[] parts;
155
156 public String getName() {
157 return name;
158 }
159
160 public void setName(String name) {
161 this.name = name;
162 }
163
164 public String getPrice() {
165 return price;
166 }
167
168 public void setPrice(String price) {
169 this.price = price;
170 }
171
172 public PartDTO[] getParts() {
173 return parts;
174 }
175
176 public void setParts(PartDTO[] parts) {
177 this.parts = parts;
178 }
179 }
180
181 public static class PartDTO {
182 //反向依賴ProductDTO
183 private ProductDTO product;
184
185 private String name;
186
187 public String getName() {
188 return name;
189 }
190
191 public void setName(String name) {
192 this.name = name;
193 }
194
195 public ProductDTO getProduct() {
196 return product;
197 }
198
199 public void setProduct(ProductDTO product) {
200 this.product = product;
201 }
202
203 }
204
205 }
206