通过上面4步,就可以定义出一个对象转换器,相比于之前来说简单很多。
定义Mapper(Bean映射器)
上面已经看了一个简单的demo,下面我们来具体了解下,如何创建或者说定义一个对象转换器,也就是定义一个Mapper。
▐ 基本的映射
创建一个bean的转换器,只需要定义一个接口,并将需要的转换方法定义在接口中,然后使用org.mapstruct.Mapper
注释对其进行注释。
比如上面的PersonMapper
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
@Mapping(source = “firstName”,target = “name”)
Person personDTOToPerson(PersonDTO personDTO);
}
@Mapper注解作用是:在build-time时,MapStruct会自动生成一个实现PersonMapper接口的类。
接口中定义的方法,在自动生成时,默认会将source对象(比如PersonDTO)中所有可读的属性拷贝到target(比如Person)对象中相关的属性,转换规则主要有以下俩条:
-
当target和source对象中属性名相同,则直接转换
-
当target和source对象中属性名不同,名字的映射可以通过@Mapping注解来指定。比如上面firstName映射到name属性上。
其实上面PersonMapper通过MapStruct生成的类和我们自己写一个转换类是没有什么区别,上面PersonMapper自动生成的实现类如下:
public class PersonMapperImpl implements PersonMapper {
public PersonMapperImpl() {
}
public Person personDTOToPerson(PersonDTO personDTO) {
if (personDTO == null) {
return null;
} else {
Person person = new Person();
person.setName(personDTO.getFirstName());
person.setLastName(personDTO.getLastName());
return person;
}
}
}
从上面可以看出,MapStruct的哲学是尽可能的生成看起来和手写的代码一样。因此,这也说明MapStruct映射对象属性使用的是getter/setter而不是反射。
正如上面例子这种显示的,在进行映射的时候,也会考虑通过@Mapping中指定的属性。如果指定的属性类型不同,MapStruct可能会通过隐式的类型转换,这个会在后面讲,或者通过调用/创建另外一个映射方法个,这个会在映射对象引用这一节说道。当一个bean的source和target属性是简单类型或者是Bean,才会创建一个新的映射方法,比如属性不能是Collection或者Map类型的属性。至于集合类型的映射将在后面讲。
MapStruct映射target和source的所有公共属性。这包括在父类型上声明的属性。
▐ 在Mapper中自定义转换属性方法
当俩种类型的映射不能通过MapStruct自动生成,我们需要自定义一些方法。自定义方法的方式主要有以下俩种。
如果其他Mapper中已经有此方法,可以在@Mapper(uses=XXXMapper.class)
来调用自定义的方法,这样可以方法重用。这个后面会说。
java8或者更新的版本,可以直接在Mapper接口中添加default方法。当参数和返回值类型匹配,则生成的代码会自动调用这个方法。
例子如下
@Mapper
public interface CarMapper {
@Mapping(…)
…
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
在MapStruct自动生成代码,需要将Person转换成PersonDTO对象时,就会直接调用default方法。
也可以使用抽象类来定义,比如上面的例子使用抽象类定义如下
@Mapper
public abstract class CarMapper {
@Mapping(…)
…
public abstract CarDto carToCarDto(Car car);
public PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
▐ 多个source参数的映射方法
MapStruct也支持带有多个source参数的映射方法。这个在将多个bean合并成一个bean的时候非常有用。
例子如下:
@Mapper
public interface AddressMapper {
@Mapping(source = “person.description”, target = “description”)
@Mapping(source = “address.houseNo”, target = “houseNumber”)
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
上面显示的就是将俩个source参数映射成一个target对象。和单个参数一样,属性映射也是通过名称。
如果多个source参数中的属性具有相同的名称,必须通过@Mapping指定哪个source里面的属性映射到target属性中。如果存在多个相同的属性,并且没有指定,则会报错。
MapStruct也支持直接引用一个source参数映射到target对象中。例子如下
@Mapper
public interface AddressMapper {
@Mapping(source = “person.description”, target = “description”)
@Mapping(source = “hn”, target = “houseNumber”)
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
上面的例子将hn直接映射到target的houseNumber属性上。
▐ 处理内嵌bean属性映射
例子如下:
@Mapper
public interface CustomerMapper {
@Mapping( target = “name”, source = “record.name” )
@Mapping( target = “.”, source = “record” )
@Mapping( target = “.”, source = “account” )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
-
如果只是某一个内嵌属性的映射,可以类似
@Mapping( target = "name", source = "record.name" )
这样写 -
如果是映射多个内嵌属性到target上,可以用
.
代替,表示把对应属性bean匹配的内嵌属性映射到target上
▐ 更新Bean实例
有时我们并不一定创建一个新的Bean,可能需要更新某一个实例。这种类型的映射我们可以通过在参数上增加一个@MappingTarget注解。例子如下:
@Mapper
public interface CarMapper {
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
这个例子会把CarDto中的属性值更新的Car对象实例上。上面的例子我们也可以将void改成Car类型返回值。
对于Collection或者Map类型,默认会将集合中所有的值清空,然后使用相关source集合中的值来填充,即CollectionMappingStrategy.ACCESSOR_ONLY策略。另外也提供了CollectionMappingStrategy.ADDER_PREFERRED 或者 CollectionMappingStrategy.TARGET_IMMUTABLE。这些策略可以在@Mapper(collectionMappingStrategy=CollectionMappingStrategy.TARGET_IMMUTABLE)来指定。
▐ 集合映射
基本的定义方式和普通的bean没什么区别,简单例子如下
@Mapper
public interface CarMapper {
Set integerSetToStringSet(Set integers);
List carsToCarDtos(List cars);
CarDto carToCarDto(Car car);
}
对应的生成方法如下
//GENERATED CODE
@Override
public Set integerSetToStringSet(Set integers) {
if ( integers == null ) {
return null;
}
Set set = new HashSet();
for ( Integer integer : integers ) {
set.add( String.valueOf( integer ) );
}
return set;
}
@Override
public List carsToCarDtos(List cars) {
if ( cars == null ) {
return null;
}
List list = new ArrayList();
for ( Car car : cars ) {
list.add( carToCarDto( car ) );
}
return list;
}
对于Map的映射,还提供了@MapMapping
注解,用于处理value的转换
具体的例子如下
public interface SourceTargetMapper {
@MapMapping(valueDateFormat = “dd.MM.yyyy”)
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
生成的代码如下
//GENERATED CODE
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
Map<Long, Date> map = new HashMap<Long, Date>();
for ( Map.Entry<String, String> entry : source.entrySet() ) {
Long key = Long.parseLong( entry.getKey() );
Date value;
try {
value = new SimpleDateFormat( “dd.MM.yyyy” ).parse( entry.getValue() );
}
catch( ParseException e ) {
throw new RuntimeException( e );
}
map.put( key, value );
}
return map;
}
通过@Mapping#collectionMappingStrategy设置集合的映射策略:CollectionMappingStrategy.ACCESSOR_ONLY:默认、CollectionMappingStrategy.SETTER_PREFERRED、CollectionMappingStrategy.ADDER_PREFERRED、CollectionMappingStrategy.TARGET_IMMUTABLE。
策略具体的意义如果没有看懂,可以参考下这篇文章MapStruct文档(五)——集合映射
▐ 枚举映射处理
直接上例子,方便理解
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(source = “EXTRA”, target = “SPECIAL”),
@ValueMapping(source = “STANDARD”, target = “DEFAULT”),
@ValueMapping(source = “NORMAL”, target = “DEFAULT”)
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
生成的代码如下
// GENERATED CODE
public class OrderMapperImpl implements OrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return null;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL;
break;
case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
写在最后
作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?
就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。
最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf”(实际上比预期多花了不少精力),包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!
由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!
Java经典面试问题(含答案解析)
阿里巴巴技术笔试心得
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf”(实际上比预期多花了不少精力),包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!
由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!
[外链图片转存中…(img-GTqIMFjO-1712466376740)]
Java经典面试问题(含答案解析)
[外链图片转存中…(img-6fztURk0-1712466376741)]
阿里巴巴技术笔试心得
[外链图片转存中…(img-eh96jGyZ-1712466376741)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算