java对象之间的属性值拷贝常用工具
公共数据定义
假设我们有两个类 Person
和 PersonDTO
,我们需要将 Person
对象转换成 PersonDTO
对象。Person
类有 firstName
, lastName
, age
属性,而 PersonDTO
类有 fullName
, age
属性。我们的任务是将 Person
中的信息映射到 PersonDTO
中。
Person 类
public class Person { private String firstName; private String lastName; private int age; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
PersonDTO 类
public class PersonDTO { private String fullName; private int age; public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
方案介绍
1. Apache Commons BeanUtils
定义:
Apache Commons BeanUtils 是一个开源的Java库,它提供了一系列操作JavaBean的工具,包括对属性的访问和设置、属性转换、克隆等。
实现原理:
- 反射机制:Apache Commons BeanUtils利用Java的反射API来访问和操作JavaBean的属性。它通过读取类的元信息(如字段、方法等)来动态地获取和设置对象的属性值。
- 属性复制:当使用
copyProperties
方法时,BeanUtils会遍历源对象的所有可读属性(即带有getter方法的属性),并尝试将这些属性的值设置到目标对象的对应属性中。这要求目标对象具有与源对象属性名相匹配的setter方法。 - 类型转换:BeanUtils还提供了类型转换功能,能够自动将一种类型的属性值转换为另一种类型,前提是这种转换是安全的和可接受的。
使用场景:
适用于简单的属性复制,但性能上可能不如直接编码或使用更专业的映射库。
示例:
firstName
和 lastName
),则需要手动编写代码。import org.apache.commons.beanutils.BeanUtils; public class Main { public static void main(String[] args) throws Exception { Person person = new Person(); person.setFirstName("John"); person.setLastName("Doe"); person.setAge(30); PersonDTO personDTO = new PersonDTO(); // 使用BeanUtils.copyProperties复制属性 BeanUtils.copyProperties(personDTO, person); // 手动设置 fullName personDTO.setFullName(person.getFirstName() + " " + person.getLastName()); System.out.println(personDTO.getFullName()); // John Doe System.out.println(personDTO.getAge()); // 30 } }
2. Spring BeanUtils
定义:
Spring框架中也提供了一个BeanUtils工具类,用于简化JavaBean之间的属性复制。
实现原理:
- 反射机制:同样基于Java反射API来实现属性复制。
- 属性匹配:通过比较源对象和目标对象的属性名(以及可能的属性类型),Spring BeanUtils将源对象的属性值复制到目标对象中。
- Spring集成:由于它是Spring框架的一部分,因此可以无缝地与Spring的其他功能(如依赖注入)集成。
区别:
Spring的BeanUtils通常用于Spring框架内部或与其相关的项目中,而Apache Commons BeanUtils是一个更通用的库。
使用场景:
与Apache Commons BeanUtils相似,但更常见于Spring项目中。
示例:
Spring Framework 的 BeanUtils
类与 Apache Commons BeanUtils 类似,但它还提供了更多的功能,比如自动类型转换。同样,它也要求属性名相同,否则需要手动处理。
import org.springframework.beans.BeanUtils; public class Main { public static void main(String[] args) { Person person = new Person(); person.setFirstName("John"); person.setLastName("Doe"); person.setAge(30); PersonDTO personDTO = new PersonDTO(); // 使用Spring的BeanUtils.copyProperties复制属性 BeanUtils.copyProperties(person, personDTO); // 手动设置 fullName personDTO.setFullName(person.getFirstName() + " " + person.getLastName()); System.out.println(personDTO.getFullName()); // John Doe System.out.println(personDTO.getAge()); // 30 } }
3. ModelMapper
定义:
ModelMapper是一个对象映射库,它提供了一个简单的API来映射对象到对象(包括标准Java对象、POJOs等)。
实现原理:
- 约定优于配置:ModelMapper采用“约定优于配置”的原则,即它试图通过智能匹配源对象和目标对象的属性名来自动建立映射关系。
- 自定义映射:如果自动匹配不满足需求,开发者可以通过注解或编程方式定义自定义的映射规则。
- 类型转换:ModelMapper支持多种类型转换,并允许开发者注册自定义的类型转换器。
- 性能优化:ModelMapper在内部使用缓存和其他优化技术来提高映射性能。
使用场景:
适用于复杂的对象映射场景,特别是当源对象和目标对象之间的属性名不完全一致时。
示例:
import org.modelmapper.ModelMapper; public class Main { public static void main(String[] args) { ModelMapper modelMapper = new ModelMapper(); Person person = new Person(); person.setFirstName("John"); person.setLastName("Doe"); person.setAge(30); // 使用 ModelMapper 映射对象 PersonDTO personDTO = modelMapper.map(person, PersonDTO.class); // 输出结果 System.out.println(personDTO.getFullName()); // John Doe System.out.println(personDTO.getAge()); // 30 } }
4. Dozer
定义:
Dozer是一个Java Bean到Java Bean的映射器,它使用XML或注解来定义映射规则。
实现原理:
- 映射文件:Dozer允许开发者通过XML文件或注解来定义复杂的映射规则。这些规则指定了如何将源对象的属性映射到目标对象的属性。
- 运行时映射:在运行时,Dozer读取这些映射规则,并根据它们将源对象的属性值复制到目标对象中。
- 可扩展性:Dozer支持自定义转换器,允许开发者处理复杂的映射场景。
使用场景:
适用于需要高度可配置映射规则的场景,特别是当项目中有多个复杂的映射需求时。
示例:
Dozer 是一个用于对象映射的工具,它允许用户通过 XML 文件或注解来配置映射规则。Dozer 也可以处理属性名不匹配的情况,并且可以进行一些基本的数据转换。
import org.dozer.DozerBeanMapper; public class Main { public static void main(String[] args) { DozerBeanMapper dozerMapper = new DozerBeanMapper(); Person person = new Person(); person.setFirstName("John"); person.setLastName("Doe"); person.setAge(30); // 使用 Dozer 映射对象 PersonDTO personDTO = dozerMapper.map(person, PersonDTO.class); // 输出结果 System.out.println(personDTO.getFullName()); // John Doe System.out.println(personDTO.getAge()); // 30 } }
5. MapStruct
定义:
MapStruct是一个基于约定优于配置原则的Java注解处理器,用于生成类型安全的bean映射类。
实现原理:
- 注解处理器:MapStruct是一个Java注解处理器,它在编译时生成类型安全的映射代码。
- 注解定义:开发者在接口上使用MapStruct提供的注解(如
@Mapper
、@Mapping
等)来定义映射规则。 - 编译时生成:在编译过程中,MapStruct注解处理器会扫描这些注解,并生成相应的映射实现类。这些实现类包含了将源对象映射到目标对象的具体逻辑。
- 性能优势:由于映射代码是在编译时生成的,因此MapStruct在运行时不依赖于反射,从而提高了映射性能。
- 类型安全:由于是编译时处理,MapStruct能够确保映射过程中的类型安全。
使用场景:
适用于性能敏感的项目,特别是在大型项目中,因为编译时生成的代码通常比运行时反射要快得多。
示例:
MapStruct 是一个代码生成器,它通过注解来定义映射接口,并自动生成实现类。这使得 MapStruct 在性能方面表现得非常好,并且可以轻松处理复杂映射逻辑。
import org.mapstruct.Mapper; import org.mapstruct.Mapping; @Mapper public interface PersonMapper { @Mapping(source = "firstName", target = "fullName") @Mapping(source = "lastName", target = "fullName", qualifiedByName = "appendSpace") @Mapping(source = "age", target = "age") PersonDTO toDto(Person person); @Named("appendSpace") default String appendSpace(String firstName, String lastName) { return firstName + " " + lastName; } } public class Main { public static void main(String[] args) { PersonMapper personMapper = PersonMapper.INSTANCE; Person person = new Person(); person.setFirstName("John"); person.setLastName("Doe"); person.setAge(30); // 使用 MapStruct 映射对象 PersonDTO personDTO = personMapper.toDto(person); // 输出结果 System.out.println(personDTO.getFullName()); // John Doe System.out.println(personDTO.getAge()); // 30 } }
每个工具都有其适用的场景和优缺点。选择哪个工具取决于具体项目的需求、性能要求以及个人或团队的偏好。对于简单的属性复制,Apache Commons BeanUtils或Spring BeanUtils可能就足够了。对于复杂的映射关系,ModelMapper、Dozer或MapStruct可能更合适。特别是MapStruct,由于其编译时生成代码的特性,它在性能上通常优于其他运行时映射工具。