JavaBean中对象的复制:BeanUtils和Dozer
在开发过程中,我们会遇到各种bean之间的转换,⽐如⽤ORM框架查询出来的数据,对应的bean,需要转换成Dto返回给调⽤⽅,这个时候就需要进⾏bean的转换了
⼀、org.springframework.beans.BeanUtils
BeanUtils是开发中常⽤到的⼯具类,⽽获取这⼀⼯具类主要是通过导⼊org.springframework.beans.BeanUtils或者
org.apachemons.beanutils.BeanUtils包来获取,但是不同的包中BeanUtils的⽅法使⽤是不⼀样的,接下来就对这两个包中的copyProperties⽅法进⾏对⽐。
先来看⼀下这两个包中的copyProperties⽅法的定义:
//org.springframework.beans.BeanUtils
public static void copyProperties(Object source, Object target){....}
//org.apachemons.beanutils.BeanUtils
public static void copyProperties(Object dest,Object orig){....}
由定义可知,在org.springframework.beans.BeanUtils包下的copyProperties第⼀个参数是被copy的对象,⽽
org.apachemons.beanutils.BeanUtils中是第⼆个参数,所以使⽤时不要弄混。
建议使⽤org.springframework.beans.BeanUtils包下的copyProperties,因为⽬标对象(target/dest)中不包含被copy的对象(source/orig)的所有字段时,apache包下的BeanUtils会报错。
源代码:
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @ ignoreProperties) throws BeansException {
Class<?> actualEditable = Class();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + Class().getName() + "] not assignable to Editable class [" + Name() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] var7 = targetPds;
int var8 = targetPds.length;
for(int var9 = 0; var9 < var8; ++var9) {
PropertyDescriptor targetPd = var7[var9];
Method writeMethod = WriteMethod();
if (writeMethod != null && (ignoreList == null || !Name()))) {
PropertyDescriptor sourcePd = Class(), Name());
if (sourcePd != null) {
Method readMethod = ReadMethod();
if (readMethod != null && ClassUtils.ParameterTypes()[0], ReturnType())) {
try {
if (!Modifier.DeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source); // 将源对象的key对应的值读取出来
if (!Modifier.DeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value); // 将源对象的值赋值到⽬标对象中
} catch (Throwable var15) {
throw new FatalBeanException("Could not copy property '" + Name() + "' from source to target", var15);
}
}
}
}
}
}
注意:复制的是属性,⽽不是字段,故要加@Data注解
1、当⽬标对象中包含源对象的所有属性时,会将源对象的所有属性值都复
制过来
Student类
@Data
public class Student {
private String id;
private String username;
private Integer age;
}
StudentDTO
@Data
public class StudentDTO{
private String id;
private String username;
private Integer age;
private String gender;
private Date birthday;
}
测试
public class BeanUtilsTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, IntrospectionException { Student student = new Student();
student.setId(UUID.randomUUID().toString());
student.setUsername("张三");
student.setAge(22);
StudentDTO studentDTO = new StudentDTO();
System.out.println(studentDTO);
}
}
结果:
StudentDTO(id=4b44fd85-1f06-4395-988f-628173f13480, username=张三, age=22, gender=null, birthday=null)
2、当⽬标对象不包含源对象的所有属性时,源对象的部分属性值会丢失Student类
@Data
public class Student {
private String id;
private String username;
private Integer age;
}
StudentDTO
@Data
public class StudentDTO{
private String id;
private String username;
}
测试
public class BeanUtilsTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, IntrospectionException { Student student = new Student();
student.setId(UUID.randomUUID().toString());
student.setUsername("张三");
student.setAge(22);
StudentDTO studentDTO = new StudentDTO();
System.out.println(studentDTO);
}
}
结果:
StudentDTO(id=4fc2e73c-3ba5-448d-8884-88f3c66bbed7, username=张三)
3、当源对象和⽬标对象中的属性的类型不⼀样时,会报错
Student类
@Data
public class Student {
private String id;
private String username;
private Integer age;
}
StudentDTO类
@Data
public class StudentDTO extends Student{
private String id;
private String username;
private Long age;
}
注意:两个类的age属性的类型不⼀样,⼀个为Integer,⼀个为Long
测试
public class BeanUtilsTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, IntrospectionException {
Student student = new Student();
student.setId(UUID.randomUUID().toString());
student.setUsername("张三");
student.setAge(22);
StudentDTO studentDTO = new StudentDTO();
System.out.println(studentDTO);
}
}
结果
Error:(16, 18) java: ity.business.StudentDTO中的getAge()⽆法覆盖ity.business.Student中的getAge()
返回类型java.lang.Long与java.lang.Integer不兼容
综上所述:pyProperties只能复制对象中名称且类型相同的属性。对于类型不同或者名称不同时,⽆法完成复制。⽽下⾯将要讲述的Dozer则能很好的解决该问题。
⼆、Dozer
Dozer是什么?
Dozer是⼀个JavaBean映射⼯具库。
它⽀持简单的属性映射,复杂类型映射,双向映射,隐式显式的映射,以及递归映射。
它⽀持三种映射⽅式:注解、API、XML。
Dozer的配置
为什么要有映射配置?
如果要映射的两个对象有完全相同的属性名,那么⼀切都很简单。
只需要直接使⽤Dozer的API即可:
Mapper mapper = new DozerBeanMapper();
DestinationObject destObject = mapper.map(sourceObject, DestinationObject.class);
但实际映射时,往往存在属性名不同的情况。
所以,你需要⼀些配置来告诉Dozer应该转换什么,怎么转换。
映射配置⽂件
在src/test/resources⽬录下添加l⽂件。
<mapping>标签中允许你定义<class-a>和<class-b>,对应着相互映射的类。
<field>标签⾥定义要映射的特殊属性。需要注意<a>和<class-a>对应,<b>和<class-b>对应,聪明的你,猜也猜出来了吧。
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="dozer.sourceforge" xmlns:xsi="/2001/XMLSchema-instance"
xsi:schemaLocation="dozer.sourceforge
dozer.sourceforge/schema/beanmapping.xsd">
<mapping date-format="yyyy-MM-dd">
<class-a>s.springmon.dozer.vo.NotSameAttributeA</class-a>
<class-b>s.springmon.dozer.vo.NotSameAttributeB</class-b>
<field>
<a>name</a>
<b>value</b>
</field>
</mapping>
</mappings>
Dozer把对象中名称相同的属性进⾏复制,对于名称不相同或类型不⼀样,则可以在xml中进⾏定义。新建⼀个springboot⼯程
1、依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-spring-boot-starter</artifactId>
<version>6.5.2</version>
</dependency>
</dependencies>
2、在application.properties中配置如下:
server.port=8002
dozer.mapping-files=classpath:l
3、启动类
java xml是什么@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4、如果两个类的属性名和类型完全⼀样
注意:此时不需要进⾏映射,即不需要l⽂件
Student类
@Data
public class Student {
private String id;
private String name;
private Integer age;
private String birthday;
}
StudentDTO
@Data
public class StudentDTO{
private String id;
private String name;
private Integer age;
private String birthday;
}
StudentController
@RestController
public class StudentController {
@Autowired
private Mapper dozerMapper;
@GetMapping("/student")
public StudentDTO getInfo(){
System.out.println("come");
Student student = new Student();
student.setId(UUID.randomUUID().toString());
student.setName("张三");
student.setAge(22);
student.setBirthday("2020-06-22");
StudentDTO studentDTO = dozerMapper.map(student,StudentDTO.class);
System.out.println(studentDTO);
return studentDTO;
}
}
{"id":"3a8a7daa-600a-413d-9bab-189728701a7a","name":"张三","age":22,"birthday":"2020-06-22"}
5、如果源对象和⽬标对象的属性名⼀样,但是类型不⼀样
修改StudentDTO类
@Data
public class StudentDTO{
private String id;
private String name;
private Long age;
private Date birthday;
}
将age由Integer改为Long,将birthday由String改为Date。如果只是将Integer改为Long,由于原型包装类和原型包装类之间可以⾃动转换,故不需要写l⽂件,但是String类型与Date类型不能直接转换,故需要l⽂件
再次访问,报错如下:
Caused by: java.lang.NumberFormatException: For input string: "2020-06-22"
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns:xsi="/2001/XMLSchema-instance"
xmlns="dozermapper.github.io/schema/bean-mapping"
xsi:schemaLocation="dozermapper.github.io/schema/bean-mapping
dozermapper.github.io/schema/bean-mapping.xsd">
<configuration>
<date-format>yyyy-MM-dd</date-format>
</configuration>
</mappings>
启动项⽬,再次访问,结果如下:
{"id":"69a13efd-d112-4aac-8a9c-e7780bb5cb6f","name":"张三","age":22,"birthday":"2020-06-21T16:00:00.000+0000"}
发现age已经转换成功,⽽birthday的样⼦有点怪。我们在StudentDTO中加@JsonFormat注解
@Data
public class StudentDTO{
private String id;
private String name;
private Long age;
@JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8")
private Date birthday;
}
启动项⽬,再次访问,结果如下:
{"id":"2d0e30a6-0f55-4629-bba4-2c2132eb7ec0","name":"张三","age":22,"birthday":"2020-06-22"}
字符串和⽇期映射(String to Date Mapping)
字符串在和⽇期进⾏映射时,允许⽤户指定⽇期的格式。
格式的设置分为三个作⽤域级别:
属性级别
对当前属性有效(这个属性必须是⽇期字符串)
<field>
<a date-format="MM/dd/yyyy HH:mm:ss:SS">dateString</a>
<b>dateObject</b>
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论