Dozer
对外接口,一般都使用特定的DTO对象,而不会使用领域模型,以避免两者的变动互相影响。其他框架适配等情形,也可能需要DTO对象。
如果手工一个个属性的在两个对象间复制有点累人,如果对象里面还有对象,就更累了。所以希望有一个工具类,可以一行代码就把对象A中的属性全部Copy到对象B中。 普通的反射框架不适合做这个事情,看看Dozer 所支持的特性就知道了:
支持两个对象间的同一属性的类型是异构的对象,比如CarDTO的engine属性是EngineDTO, 而Car的engine属性是Engine。
支持String <-> 基础类型的转换,比如CarDTO的price属性是String, 而Car的price属性是Double.
支持Collection类型间的转换,比如String[] <-> List
支持双向依赖,比如Product有个属性是List parts, 而每个Part也有一个Product属性,此时Product与Part双向依赖了。
属性名实在不一致时,可以用@Mapping定义,而且只在其中一边定义就可以了。
但Dozer 也有个缺点,必须基于getter/setter,不能直接访问public field,卡住了我让Entity/DTO都取消掉getter/setter的计划。
Dozer已比较成熟,所以更新很慢。另一个类似但更新较勤快的项目叫Orika
In SpringSide
在core中封装了一个BeanMapper,实现如下功能:
持有Dozer单例, 避免重复创建DozerMapper消耗资源.
自动泛型类型转换.
批量转换Collection中的所有对象.
区分创建新的B对象与将对象A值赋值到已存在的B对象的函数.
在showcase中有一个DozerDemo,详细演示了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如何只要属性名相同,可以罔顾属性类型是基础类型<->String的差别,Array转为List,甚至完全另一种DTO的差别。
13 * 并且能完美解决循环依赖问题。
14 * 使用@Mapping能解决属性名不匹配的情况.
15 */
16 public class DozerDemo {
17
18 /**
19 * 从一个ProductDTO实例,创建出一个新的Product实例。
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中循环依赖的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 * 演示将一个ProductDTO实例的内容,Copy到另一个已存在的Product实例.
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实例
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中循环依赖的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,只要在一边定义,双向转换都可以使用.
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