在Java项目里做对象转换时,经常会遇到一种需求:只有当源对象的某个字段不为空,才把目标对象的另一个字段设成固定的常量值。MapStruct作为编译期生成映射代码的工具,能够通过条件方法与常量配置配合来实现这个逻辑,而且不会带来运行时的反射开销。

一、基础依赖与实体定义
使用MapStruct前,需要在构建工具中引入对应的注解处理器依赖。以Maven为例,核心依赖包含mapstruct本身以及配套的processor,后者会在编译阶段扫描接口并生成实现类。如果遗漏processor,项目虽然能编译通过,但运行时会出现实现类找不到的异常。
下面给出两个简单的实体类,源对象含有用户昵称,目标对象含有一个渠道常量字段,后续所有映射都围绕这两个类展开。
// 源对象
public class UserSource {
private String nickName;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
// 目标对象
public class OrderTarget {
private String channel;
private String userName;
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
二、条件方法的编写规则
MapStruct的条件映射依赖一个返回boolean的方法,方法名通过@Condition指定。该方法接收源字段作为参数,返回true时对应的映射逻辑才会执行。需要注意的是,条件方法必须写在映射接口或者抽象的映射类里,且方法名不能与映射方法重名。
针对昵称非空判断,可以写一个返回boolean的方法,当入参不为null且去除空格后长度大于零时返回true。这样在源字段为空或纯空格时,条件不成立,目标字段就不会被设置。
@Condition
public boolean isNickNameValid(String nickName) {
return nickName != null && nickName.trim().length() > 0;
}
三、在映射接口中组合常量值
有了条件方法后,在@Mapping中同时配置source、target、condition与constant。constant表示目标字段要写入的固定值,condition引用前面定义的判断方法。当源字段满足条件,MapStruct生成的代码会把常量赋给目标字段;不满足条件时则跳过赋值。
以下接口展示了完整用法,MapStruct在编译后会生成判断nickName有效性再写channel为APP的逻辑,userName则正常映射。
@Mapper
public interface UserOrderMapper {
UserOrderMapper INSTANCE = Mappers.getMapper(UserOrderMapper.class);
@Condition
public boolean isNickNameValid(String nickName) {
return nickName != null && nickName.trim().length() > 0;
}
@Mapping(
target = "channel",
source = "nickName",
condition = "isNickNameValid",
constant = "APP"
)
@Mapping(target = "userName", source = "nickName")
OrderTarget toOrder(UserSource source);
}
四、生成的代码与调用示例
编译后,MapStruct会自动生成一个实现类,内部逻辑等价于先判断条件再赋值常量。我们可以写一段测试代码验证行为:当昵称为空时,channel为null;当昵称有值时,channel变成APP。
这种方式的优势在于逻辑在编译期固化,没有运行时的反射判断,也比手动写if-else更不容易漏掉边界情况。
public class TestDemo {
public static void main(String[] args) {
UserSource src1 = new UserSource();
src1.setNickName(null);
OrderTarget t1 = UserOrderMapper.INSTANCE.toOrder(src1);
System.out.println(t1.getChannel()); // 输出 null
UserSource src2 = new UserSource();
src2.setNickName("张三");
OrderTarget t2 = UserOrderMapper.INSTANCE.toOrder(src2);
System.out.println(t2.getChannel()); // 输出 APP
}
}
五、常见误区与注意点
一个容易踩的坑是误以为constant可以配合defaultValue实现条件写入。defaultValue是在源字段为null时使用的默认值,它不与condition联动,无法实现“非空才写常量”的语义。只有condition加constant才是正确组合。
另一个注意点是,如果目标字段本身还有别的映射来源,constant会覆盖其他配置,因此建议把这种固定值字段单独用一条@Mapping管理,避免和正常字段转换混在一起造成阅读困难。
| 方案 | 是否支持非空写常量 | 性能 |
|---|---|---|
| BeanUtils手动判断 | 支持 | 反射较慢 |
| MapStruct condition+constant | 支持 | 编译期代码无反射 |
| defaultValue | 不支持 | 编译期代码无反射 |
六、总结
通过@Condition定义非空判断,再在@Mapping里用constant指定固定值,MapStruct能够以类型安全且零运行时开销的方式完成条件映射。该做法特别适合把业务标识、来源渠道等固定内容写进目标对象,同时避免手写的判空逻辑分散在多个地方。
在实际开发中,建议把条件方法集中放在映射接口中,并配合清晰的字段命名,这样生成的映射代码更易维护,也方便后续扩展更多条件规则。