MapStruct 是一个 Java注释处理器,用于生成类型安全的 bean 映射类。
您所要做的就是定义一个映射器接口,该接口声明任何所需的映射方法。在编译期间,MapStruct 将生成该接口的实现。这个实现使用普通的Java 方法调用来映射源对象和目标对象,即没有反射或类似的。
与手工编写映射代码相比,MapStruct 通过生成繁琐且容易出错的代码来节省时间。遵循配置方法的约定,MapStruct 使用合理的默认值,但在配置或实现特殊行为时会采取措施。
与动态映射框架相比,MapStruct 具有以下优点:
通过使用普通方法调用而不是反射来快速执行
编译时类型安全:只能映射相互映射的对象和属性,不能将订单实体意外映射到客户 DTO 等。
在构建时清除错误报告,如果
MapStruct 注解的关键词
@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
@Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个
default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入
@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
source:源属性
target:目标属性
dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
ignore: 忽略这个字段
@Mappings:配置多个@Mapping
@MappingTarget 用于更新已有对象
@InheritConfiguration 用于继承配置
Orika是java Bean映射框架,可以实现从一个对象递归拷贝数据至另一个对象。在开发多层应用程序中非常有用。在这些层之间交换数据时,通常为了适应不同API需要转换一个实例至另一个实例。
有很多方法可以实现:硬代码拷贝或Dozer实现bean映射等。总之,需要简化不同层对象之间映射过程。
Orika使用字节码生成器创建开销最小的快速映射,比其他基于反射方式实现(如,Dozer)更快。之前使用Bean Copy 性能非常慢,发现在这个领域业界还是有很多新秀的。 Orika 应该就算一个比较好的吧。
工具 | 实现方式 | 缺点 | 说明 |
---|---|---|---|
mapstruct | getter/setter方法 | 需要了解注解和配置项语法 | JSR269注解处理器在编译期自动生成Java Bean转换代码,支持可配置化,扩展性强 |
orika | 动态生成字节码 | 首次调用耗时较久,性能适中 | 采用javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件 |
官方文档: https://mapstruct.org/documentation/stable/reference/html/
案例教程:https://blog.csdn.net/Z143430039/article/details/111054479 https://blog.csdn.net/zhige_me/article/details/80699784
<properties>
<mapstruct.version>1.4.1.Final </mapstruct.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- commons工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- 测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
<!-- mapstruct和lombok整合配置-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
private Long id;
private String name;
private String email;
private Date birthday;
private User user;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private Integer age;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PersonDTO {
private Long id;
private String name;
/**
* 对应 Person.user.age
*/
private Integer age;
private String email;
/**
* 与 DO 里面的字段名称(birthDay)不一致
*/
private Date birth;
/**
* 对 DO 里面的字段(birthDay)进行拓展,dateFormat 的形式
*/
private String birthDateFormat;
/**
* 对 DO 里面的字段(birthDay)进行拓展,expression 的形式
*/
private String birthExpressionFormat;
}
@Mapper
public interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings({
@Mapping(source = "birthday", target = "birth"),
@Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
@Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
@Mapping(source = "user.age", target = "age"),
@Mapping(target = "email", ignore = true)
})
PersonDTO domain2dto(Person person);
List<PersonDTO> domain2dto(List<Person> people);
/**
*自定义实现(如果你想手写)
*/
default LocalDate map(LocalDateTime time) {
return time.toLocalDate();
}
}
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-04-07T15:07:34+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_40 (Oracle Corporation)"
)
public class PersonConverterImpl implements PersonConverter {
@Override
public PersonDTO domain2dto(Person person) {
if ( person == null ) {
return null;
}
PersonDTO personDTO = new PersonDTO();
personDTO.setBirth( person.getBirthday() );
if ( person.getBirthday() != null ) {
personDTO.setBirthDateFormat( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( person.getBirthday() ) );
}
personDTO.setAge( personUserAge( person ) );
personDTO.setId( person.getId() );
personDTO.setName( person.getName() );
personDTO.setBirthExpressionFormat( org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),"yyyy-MM-dd HH:mm:ss") );
return personDTO;
}
@Override
public List<PersonDTO> domain2dto(List<Person> people) {
if ( people == null ) {
return null;
}
List<PersonDTO> list = new ArrayList<PersonDTO>( people.size() );
for ( Person person : people ) {
list.add( domain2dto( person ) );
}
return list;
}
private Integer personUserAge(Person person) {
if ( person == null ) {
return null;
}
User user = person.getUser();
if ( user == null ) {
return null;
}
Integer age = user.getAge();
if ( age == null ) {
return null;
}
return age;
}
}
官方文档: http://orika-mapper.github.io/orika-docs/mappings-via-classmapbuilder.html
//处理不同属性的映射
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(User.class, UserVo.class)
.field("name", "userName")
.field("age", "ageOne")
.byDefault().register();
UserVo userVo = mapperFactory.getMapperFacade().map(User, UserVo.class);
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.6</version>
</dependency>
public enum MapperUtils {
/**
* 实例
*/
INSTANCE;
/**
* 默认字段工厂
*/
private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
/**
* 默认字段实例
*/
private static final MapperFacade MAPPER_FACADE = MAPPER_FACTORY.getMapperFacade();
/**
* 默认字段实例集合
*/
private static Map<String, MapperFacade> CACHE_MAPPER_FACADE_MAP = new ConcurrentHashMap<>();
/**
* 映射实体(默认字段)
*
* @param toClass 映射类对象
* @param data 数据(对象)
* @return 映射类对象
*/
public <E, T> E map(Class<E> toClass, T data) {
return MAPPER_FACADE.map(data, toClass);
}
/**
* 映射实体(自定义配置)
*
* @param toClass 映射类对象
* @param data 数据(对象)
* @param configMap 自定义配置
* @return 映射类对象
*/
public <E, T> E map(Class<E> toClass, T data, Map<String, String> configMap) {
MapperFacade mapperFacade = this.getMapperFacade(toClass, data.getClass(), configMap);
return mapperFacade.map(data, toClass);
}
/**
* 映射集合(默认字段)
*
* @param toClass 映射类对象
* @param data 数据(集合)
* @return 映射类对象
*/
public <E, T> List<E> mapAsList(Class<E> toClass, Collection<T> data) {
return MAPPER_FACADE.mapAsList(data, toClass);
}
/**
* 映射集合(自定义配置)
*
* @param toClass 映射类
* @param data 数据(集合)
* @param configMap 自定义配置
* @return 映射类对象
*/
public <E, T> List<E> mapAsList(Class<E> toClass, Collection<T> data, Map<String, String> configMap) {
T t = data.stream().findFirst().orElseThrow(() -> new ExceptionInInitializerError("映射集合,数据集合为空"));
MapperFacade mapperFacade = this.getMapperFacade(toClass, t.getClass(), configMap);
return mapperFacade.mapAsList(data, toClass);
}
/**
* 获取自定义映射
*
* @param toClass 映射类
* @param dataClass 数据映射类
* @param configMap 自定义配置
* @return 映射类对象
*/
private <E, T> MapperFacade getMapperFacade(Class<E> toClass, Class<T> dataClass, Map<String, String> configMap) {
String mapKey = dataClass.getCanonicalName() + "_" + toClass.getCanonicalName();
MapperFacade mapperFacade = CACHE_MAPPER_FACADE_MAP.get(mapKey);
if (Objects.isNull(mapperFacade)) {
MapperFactory factory = new DefaultMapperFactory.Builder().build();
ClassMapBuilder classMapBuilder = factory.classMap(dataClass, toClass);
configMap.forEach(classMapBuilder::field);
classMapBuilder.byDefault().register();
mapperFacade = factory.getMapperFacade();
CACHE_MAPPER_FACADE_MAP.put(mapKey, mapperFacade);
}
return mapperFacade;
}
}
@Test
public void test1() {
Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
//默认映射
PersonDTO userDTO = MapperUtils.INSTANCE.map(PersonDTO.class, person);;
//自定义映射
Map<String, String> config = new HashMap<>();
// 自定义配置(birthday 转 birth)
config.put("birthday", "birth");
config.put("user.age", "age");
PersonDTO userDomain = MapperUtils.INSTANCE.map(PersonDTO.class, person, config);
System.out.println(userDomain);
//默认 PersonDTO(id=1, name=zhige, age=null, email=zhige.me@gmail.com, birth=null, birthDateFormat=null, birthExpressionFormat=null)
//加confi PersonDTO(id=1, name=zhige, age=1, email=zhige.me@gmail.com, birth=Thu Apr 07 15:53:39 CST 2022, birthDateFormat=null, birthExpressionFormat=null)
Person person2 = new Person(11L,"zhansan","zhansan.me@gmail.com",new Date(),new User(21));
Person person3 = new Person(12L,"lisi","lisi.me@gmail.com",new Date(),new User(31));
List<Person> userDOList =new ArrayList<>();
userDOList.add(person);
userDOList.add(person2);
userDOList.add(person3);
List<PersonDTO> userDTOS = MapperUtils.INSTANCE.mapAsList(PersonDTO.class,userDOList);
List<PersonDTO> userDTOS1 = MapperUtils.INSTANCE.mapAsList(PersonDTO.class,userDOList,config);
}