阿里巴巴出的的《阿里巴巴Java开发手册》里面制定了一个工程规约,第一条就是关于应用分层的,如下:
按这个手册上说的,一般分为如下几层:
开放接口层
终端显示层
Web层
Service层
Manager层
Dao层
外部接口或第三方平台
在实际开发工作中比较常用的分层是Web层(Controller)、Service层、Dao层(Mapper)等3层。
每一层的数据对象都放在各自的领域模型对象中,按照手册里面的分层领域模型规约,分为这几种
DO (Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
DTO(Data Transfer Object):数据传输对象,Service和Manager向外传输的对象。
BO(Business Object):业务对象。可以由Service层输出的封装业务逻辑的对象。
QUERY:数据查询对象,各层接收上层的查询请求。注:超过2个参数的查询封装,禁止使用Map类来传输。
VO(View Object):显示层对象,通常是Web向模板引擎层传输的对象。
除了手册里面的 还有PO持久化对象和POJO普通Java对象。
在实际项目开发中, 没必要弄这么多种区分,一般常用是VO/DTO和PO/Entity这2种。通常保证业务逻辑层Service和数据库DAO层的操作对象严格划分出来,确保互相不渗透,不混用就行。也就是说:Controller层的数据对象不要直接渗透到DAO层,同理数据表实体对象Entity/PO也不要直接传到Controller层进行输出或展示。
在开发中Web(Controller)层调用Service层,然后Service层再调用Dao层,上层调用下层涉及到了层与层之间领域对象的转换。
一般有如下几种方式:
本文要介绍的是MapStruct的使用,MapStruct生成的映射代码使用的是普通的方法调用,因此速度快、类型安全且易于理解。
官方地址:https://mapstruct.org/
MapStruct是一个Java注解处理器,用于生成类型安全的bean映射类。
您所要做的就是定义一个mapper接口,该接口声明任何必需的映射方法。
在编译过程中,MapStruct将生成此接口的实现类。这个实现类使用普通的Java方法调用来映射源对象和目标对象,即没有使用反射或类似的的技术。
与手工编写映射代码相比,MapStruct通过生成冗长且容易出错的代码来节省时间。遵循约定优于配置的方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时,它会跳出你的方式。
与动态映射框架相比,MapStruct提供了以下优势:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.1.Final</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
注意事项:
1.确保您的项目使用的是Java 1.8或更高版本
2.Maven插件版本要3.6以上。
3.如果要结合Lombok一起使用,则Lombok版本要1.16.16版本以上,并且配置改成如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
@Mapper
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname",target = "ename")
EmpVo empToEmpVo(Emp emp);
}
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setDeptno(emp.getDeptno());
empVo.setDname(emp.getDname());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
return empVo;
}
}
}
MapStruct的一般原理是生成尽可能多的代码,就好像是你自己写的一样。特别是,这意味着通过普通的getter/setter调用而不是反射或类似的方法将值从源复制到目标。
假设从A映射到B需要一些特殊的逻辑,需要特定的条件才能进行映射。一种方法是在B上实现一个自定义方法,然后由MapStruct生成的映射器调用这个自定义方法。
还有一种方法就是使用Java8的默认方法,在默认方法中手写映射逻辑。
@Mapper
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname",target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
* 使用默认方法实现特殊的映射逻辑
* @param sysuser
* @return
*/
default SysuserVo sysUserToSysuserVo(Sysuser sysuser){
}
映射器也可以用抽象类的形式定义,而不是接口的形式,并直接在映射器类中实现自定义方法。在这种情况下,MapStruct将生成抽象类的扩展,实现所有的抽象方法。与声明默认方法相比,此方法的一个优点是可以在mapper类中声明其他字段。
MapStruct还支持带有多个源参数的映射方法。例如为了将多个实体合并成一个视图对象。
/**
* 在使用@Mapping注释时,必须指定属性所在的参数。
* @param emp
* @param dept
* @return
*/
@Mapping(source = "emp.empname", target = "ename")
@Mapping(source = "emp.remark",target = "remark")
@Mapping(source = "emp.deptno",target = "deptno")
@Mapping(source = "dept.dname", target = "empDeptName")
EmpVo empAndDeptToEmpVo(Emp emp, Dept dept);
上面这个方法接受两个源参数并返回一个组合的目标对象。与单参数映射方法一样,属性是按名称映射的。
如果多个源对象定义了具有相同名称的属性,则必须使用@Mapping注解指定用于检索属性的源参数,如示例中的deptno属性所示。当这样的歧义没有解决时,将会引发错误。对于在给定源对象中只存在一次的属性,可以选择指定源参数的名称,因为它可以自动确定。
如果所有源参数都为空,则带有多个源参数的映射方法将返回null。否则,将实例化目标对象,并传播所提供参数的所有属性。
MapStruct还提供了直接引用源参数的可能性,例如,源参数不是bean类型
@Mapping(source = "emp.empname", target = "ename")
@Mapping(source = "dname", target = "empDeptName")
EmpVo fromEmp(Emp emp, String dname);
在本例中,源参数被直接映射到目标中,如上例所示。参数dname是一个非bean类型(在本例中是java.lang.String),它被映射到dname属性字段上。
有时候不需要创建目标类型的新实例,而是希望更新该类型的现有实例。这种映射可以通过为目标对象添加一个参数并使用@MappingTarget标记这个参数来实现
/**
* 更新已有的emp对象
* @param empVo
* @param emp
*/
void updateEmpFromVo(EmpVo empVo,@MappingTarget Emp emp);
MapStruct还支持没有getter /setter的公共字段的映射。如果MapStruct不能找到适合属性的getter/setter方法,它将使用字段作为读/写访问器。
如果字段是public或public final,则将其视为读访问器。如果字段是静态的,则不将其视为读访问器。
只有在字段是公共的情况下,才将其视为写访问器。如果字段是final和/或静态的,则不认为它是写访问器。
Emp emp = empService.getByEmpno("592");
EmpConvert empConvert = Mappers.getMapper(EmpConvert.class);
EmpVo empVo = empConvert.empToEmpVo(emp);
按照惯例,mapper接口应该定义一个名为INSTANCE的成员,该成员持有mapper类型的单个实例
@Mapper
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
这种模式使客户端很容易使用mapper对象,而无需重复实例化新实例:
EmpVo empVo = EmpConvert.INSTANCE.empToEmpVo(emp);
注意,MapStruct生成的映射器是无状态的,并且是线程安全的,因此可以同时从多个线程安全地访问。
如果您使用的是依赖项注入框架,比如CDI 或Spring框架,那么建议您通过依赖项注入来获取mapper对象,而不是通过上面描述的Mappers类
@Mapper(componentModel = "spring")
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
* 注入emp映射器
*/
@Resource
private EmpConvert empConvert;
public EmpVo getEmp() {
Emp emp = empService.getByEmpno("592");
return empConvert.empToEmpVo(emp);
}
在许多情况下,MapStruct会自动处理类型转换。例如,如果一个属性字段在源bean中是int类型的,而在目标bean中是String类型的,那么生成的映射器代码将通过分别调用字符串#valueOf(int)和整数#parseInt(String)来透明地执行转换。
会自动进行类型转换的有以下几种情况:
通常,一个对象不仅具有基本属性,而且还引用其他对象。例如,Emp类可以包含一个对Dept对象的引用,这个引用应该被EmpVo类映射到一个DeptVo对象。
public class Emp {
/**
* 员工编号
*/
private Long empno;
/**
* 所在部门对象
*/
private Dept dept;
......
public class EmpVo {
/**
* 员工编号
*/
private Long empno;
/**
*/
private DeptVo deptVo;
......
emp映射器如下:
@Mapper(componentModel = "spring")
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept",target = "deptVo")
EmpVo empToEmpVo(Emp emp);
DeptVo deptToDeptVo(Dept dept);
emp映射器生成的代码如下:
@Component
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setDeptVo(this.deptToDeptVo(emp.getDept()));
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setDeptno(emp.getDeptno());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
empVo.setRemark(emp.getRemark());
return empVo;
}
}
public DeptVo deptToDeptVo(Dept dept) {
if (dept == null) {
return null;
} else {
DeptVo deptVo = new DeptVo();
deptVo.setDeptno(dept.getDeptno());
deptVo.setDname(dept.getDname());
deptVo.setLoc(dept.getLoc());
deptVo.setRemark(dept.getRemark());
return deptVo;
}
}
这样就可以映射任意深度对象层次。
在生成映射方法的实现时,MapStruct将对源和目标对象中的每个属性将遵从以下规则:
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(target = "deptVo",ignore = true)
EmpVo empToEmpVo(Emp emp);
gnore = true 设置忽略deptVo属性
当从实体映射到视图对象时,在某一点上减少对其他实体的引用通常是有用的。为此,实现一个自定义映射方法,例如将引用的dept实体映射到目标对象中的empDeptName和deptno上,
public class Emp {
/**
* 员工编号
*/
private Long empno;
/**
* 所在部门对象
*/
private Dept dept;
......
public class EmpVo {
/**
* 员工编号
*/
private Long empno;
/**
* 所在部门编号
*/
private int deptno;
/**
* 部门名称
*/
private String empDeptName;
...
映射器代码如下:
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
生成的映射器实现类如下:
@Component
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setEmpDeptName(this.empDeptDname(emp));
Integer deptno = this.empDeptDeptno(emp);
if (deptno != null) {
empVo.setDeptno(deptno);
}
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
empVo.setRemark(emp.getRemark());
return empVo;
}
}
private String empDeptDname(Emp emp) {
if (emp == null) {
return null;
} else {
Dept dept = emp.getDept();
if (dept == null) {
return null;
} else {
String dname = dept.getDname();
return dname == null ? null : dname;
}
}
}
private Integer empDeptDeptno(Emp emp) {
if (emp == null) {
return null;
} else {
Dept dept = emp.getDept();
if (dept == null) {
return null;
} else {
Integer deptno = dept.getDeptno();
return deptno == null ? null : deptno;
}
}
}
除了在同一mapper类型上定义的方法之外,MapStruct还可以调用在其他类中定义的映射方法,无论是由MapStruct生成的映射器,还是手工编写的映射方法。这对于在多个类中构造映射代码(例如,每个应用程序模块使用一个映射器类型)或者提供不能由MapStruct生成的自定义映射逻辑是很有用的。
例如,Emp类可能包含一个属性updateTime,类型是Timestamp,而相应的Vo属性的类型是String。为了映射这个属性,你可以像这样实现一个自定义转换类:
public class TimestampConvert {
public String timeStampToString(Timestamp updateTime){
return updateTime.toString();
}
}
在EmpConvert接口的@Mapper注释中,引用了这样的TimestampConvert 类:
@Mapper(uses = TimestampConvert.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
在EmpConvert 接口的实现类中,MapStruct将查找一个将Timestamp对象映射到字符串的方法,在TimestampConvert类中找到它,并生成一个timeStampToString()调用来映射updateTime属性。
EmpConvert 接口实现类如下:
public class EmpConvertImpl implements EmpConvert {
private final TimestampConvert timestampConvert = new TimestampConvert();
@Override
public EmpVo empToEmpVo(Emp emp) {
if ( emp == null ) {
return null;
}
EmpVo empVo = new EmpVo();
empVo.setEname( emp.getEmpname() );
empVo.setEmpDeptName( empDeptDname( emp ) );
Integer deptno = empDeptDeptno( emp );
if ( deptno != null ) {
empVo.setDeptno( deptno );
}
empVo.setEmpno( emp.getEmpno() );
empVo.setJob( emp.getJob() );
empVo.setMgr( emp.getMgr() );
empVo.setHiredate( emp.getHiredate() );
empVo.setSal( emp.getSal() );
empVo.setComm( emp.getComm() );
empVo.setCreateTime( emp.getCreateTime() );
empVo.setUpdateTime( timestampConvert.timeStampToString( emp.getUpdateTime() ) );
empVo.setRemark( emp.getRemark() );
return empVo;
}
注意点:如果是采用依赖注入的方式来调用映射器,则当前映射器调用的其他映射器也要是可注入的
@Mapper(componentModel = "spring",uses = TimestampConvert.class)
public interface EmpConvert {
......
}
/**
* 自定义时间转换器
*
* @author David Lin
* @version: 1.0
* @date 2020-04-12 17:24
*/
@Component
public class TimestampConvert {
public String timeStampToString(Timestamp updateTime){
return updateTime.toString();
}
}
…
…
在将属性从一种类型映射到另一种类型时,MapStruct查找将源类型映射到目标类型的最特定方法。该方法可以在相同的mapper接口上声明,也可以在另一个通过@Mapper#uses()注册的mapper上声明。这同样适用于工厂方法。
…
集合类型(List、Set等)的映射与映射bean类型的映射方式相同,即在mapper接口中使用所需的源和目标类型定义映射方法。MapStruct支持来自Java集合框架的广泛的可迭代类型。
生成的代码将包含一个循环,该循环遍历源集合,转换每个元素并将其放入目标集合。如果在给定的映射器或其使用的映射器中找到集合元素类型的映射方法,则调用此方法来执行元素转换。或者,如果存在源和目标元素类型的隐式转换,则将调用此转换例程
@Mapper(componentModel = "spring")
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param empList
* @return
*/
List<EmpVo> empsToEmpVos(List<Emp> empList);
有时需要在某些映射方法之前或之后应用自定义逻辑。MapStruct提供了两种方法:装饰器,它允许对特定映射方法进行类型安全的定制;映射前和映射后的生命周期方法,它允许对具有给定源或目标类型的映射方法进行通用定制。
(1).使用装饰器定制映射
在某些情况下,可能需要自定义一个生成的映射方法,例如,在目标对象中设置一个不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持这一需求。
要将装饰器应用到映射器类,请使用@DecoratedWith注释指定它。
装饰器必须是装饰的映射器类型的子类型。您可以使它成为一个抽象类,它只允许实现您想要自定义的mapper接口的那些方法。对于所有未实现的方法,将使用默认的生成例程生成对原始映射器的简单委托。
比如Emp转EmpVo类型时,想自定义job字段值,如下:
@Mapper(componentModel = "spring",uses = TimestampConvert.class)
@DecoratedWith(EmpDecorator.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
/**
* emp类型转换装饰器
*
* @author David Lin
* @version: 1.0
* @date 2020-05-02 9:35
*/
public abstract class EmpDecorator implements EmpConvert {
@Resource
private EmpConvert delegate;
@Override
public EmpVo empToEmpVo(Emp emp) {
EmpVo empVo = delegate.empToEmpVo(emp);
empVo.setJob("当前员工的工作岗位是:"+empVo.getEname());
return empVo;
}
}
@RequestMapping("/testEmp")
public EmpVo testTime(){
Emp emp = new Emp();
emp.setEmpname("smith");
emp.setEmpno(123L);
emp.setJob("开发");
Timestamp updateTime = new Timestamp(System.currentTimeMillis());
emp.setUpdateTime(updateTime);
EmpVo empVo =empConvert.empToEmpVo(emp);
logger.info("the empVo updateTiem is {}",empVo.getUpdateTime());
return empVo;
}
(2).使用前映射和后映射方法进行定制映射
在定制映射器时,装饰器可能并不总是适合需要。您可以使用在映射开始之前或映射完成之后调用的回调方法。
回调方法可以在抽象映射器本身中实现,也可以在mapper #uses中的类型引用中实现,或者在用作@Context参数的类型中实现。
这里采用mapper#uses实现
**
* emp映射方法增强
*
* @author David Lin
* @version: 1.0
* @date 2020-05-02 10:54
*/
@Component
public class EmpAop {
@BeforeMapping
public void setEmpJob(Emp emp){
emp.setJob("员工工作岗位:"+emp.getJob());
}
@AfterMapping
public void setRemark(Emp emp, @MappingTarget EmpVo empVo){
empVo.setRemark(emp.getEmpname()+":"+emp.getRemark());
}
}
@Mapper(componentModel = "spring",uses = {
TimestampConvert.class,EmpAop.class})
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
如果@BeforeMapping / @AfterMapping方法有参数,那么只有当方法的返回类型(如果非void)可以赋值给映射方法的返回类型,并且所有参数都可以由映射方法的源或目标参数赋值时,才会生成方法调用 。
…
这里描述了几个高级选项,这些选项允许根据需要对生成的映射代码的行为进行微调。
如果相应的源属性为空,可以指定默认值将预定义值设置为目标属性。在任何情况下都可以指定常量来设置这样的预定义值。默认值和常量被指定为字符串值。当目标类型是原语类型或已装箱类型时,将获取字符串值的文字值。位/八进制/十进制/十六进制模式在这种情况下是允许的,只要它们是有效的文字。在所有其他情况下,常量或默认值都要通过内置转换或调用其他映射方法进行类型转换
具有常量的映射不能包含对源属性的引用
@Mapper(componentModel = "spring",uses = {
TimestampConvert.class,EmpAop.class})
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName",defaultValue = "开发部门")
@Mapping(source="dept.deptno",target = "deptno")
@Mapping(target = "updateTime",dateFormat = "yyyy-MM-dd",constant ="2020-05-02")
EmpVo empToEmpVo(Emp emp);
生成的代码如下:
if (this.empDeptDname(emp) != null) {
empVo.setEmpDeptName(this.empDeptDname(emp));
} else {
empVo.setEmpDeptName("开发部门");
}
empVo.setUpdateTime("2020-05-02");
…
默认表达式是默认值和表达式的组合。它们只在源属性为空时使用。
同样的警告和限制也适用于默认表达式。只支持Java,而MapStruct不会在生成时验证表达式。
@Mapper(componentModel = "spring", imports = PlatStringUtil.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname", target = "empDeptName", defaultValue = "开发部门")
@Mapping(source = "dept.deptno", target = "deptno")
@Mapping(target = "updateTime", dateFormat = "yyyy-MM-dd", constant = "2020-05-02")
@Mapping(source = "remark", target = "remark", defaultExpression = "java(PlatStringUtil.randomUUID())")
EmpVo empToEmpVo(Emp emp);
上面这个例子演示了如何使用defaultExpression来设置一个remark字段(如果源字段为null),如果源对象设置了现有的remark,则可以使用它来获取它,如果没有设置,则创建一个新ID。请注意,指定了完全限定的包名,因为MapStruct不负责PlatStringUtil类的导入(除非在EmpConvert 中显式地使用它)。可以通过在@Mapper注释上定义导入来解决这个问题
当结果类型具有继承关系时,选择映射方法(@Mapping)或工厂方法(@BeanMapping)可能会变得模糊。
当映射方法的源参数等于null时,MapStruct提供对要创建的对象的控制。默认情况下将返回null。
但是,通过指定nullValueMappingStrategy = nullValueMappingStrategy。在@BeanMapping、@IterableMapping、@MapMapping或@Mapper或@MappingConfig上使用RETURN_DEFAULT,映射结果可以更改为返回空的默认值。
几种类型null值返回如下:
优先级:
在映射方法级别上设置nullValueMappingStrategy属性优先于@Mappe上设置, ,而@Mapper#nullValueMappingStrategy优先于@MappingConfig#nullValueMappingStrategy。
当源属性等于null或状态检查方法导致“缺席”时,MapStruct提供了对@MappingTarget带注释的目标bean中设置的属性的控制。
默认情况下,目标属性将设置为null。
MapStruct提供了何时生成空检查的控制,默认情况下(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION))会生成一个空检查:
…
当设置nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS时,如果源不是基本类型,则始终包含一个空检查,除非在源bean上定义了源状态检查器。
一些框架生成具有源状态检查器的bean属性。通常这是以hasXYZ方法的形式出现的,XYZ是bean映射方法中源bean上的一个属性。当MapStruct发现hasXYZ方法时,它将调用这个hasXYZ,而不是执行空检查。
在调用映射方法时,调用应用程序可能需要处理异常。这些异常可以通过手工编写的逻辑和生成的内置映射方法或MapStruct的类型转换来抛出。当调用应用程序需要处理异常时,可以在映射方法中定义一个抛出子句: