我在自己的项目中引入了 MapStruct【https://mapstruct.org/】用于完成对象间映射转换。
MapStruct框架的作用不过多解释了,想要了解的可以看下它的官网介绍。主要作用就是可以通过配置,定义对象和对象之间的映射,减轻编码负担。
MapStruct性能很高。之所以快,是因为它没有用反射。之所以能不用反射,是因为它是在程序编译期间,直接生成了对象转换的类文件。
这段比较抽象,但用过lombok的话就好理解了。它和lombok很相似,主要工作是在编译期完成,不是运行时。
1. 问题
下面是我遇到的问题,具体逻辑可以不用太纠结,大概知道我要干啥就行了。
首先,我使用MaStruct定义了如下的接口:
@Mapper(uses = {JsonStrMapper.class, PrimitiveNullMapper.class},
componentModel = "spring",
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
public interface UserPropertyMapper {
@SuccessResponse
@Mapping(target = "basic", qualifiedByName = {"JsonStrMapper", "BeanMapToStrMap"})
@Mapping(target = "credit", qualifiedByName = {"JsonStrMapper", "BeanMapToStrMap"})
@Mapping(target = "growth", qualifiedByName = {"JsonStrMapper", "BeanMapToStrMap"})
@Mapping(target = "tag", qualifiedByName = {"JsonStrMapper", "BeanMapToStrMap"})
GetUserPropertyResponse entityToGetResponse(UserPropertyEntity entity) throws MapperException;
}
MapStruct生成的源代码如下:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-01-26T11:24:54+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_272 (AdoptOpenJDK)"
)
@Component
public class UserPropertyMapperImpl implements UserPropertyMapper {
@Override
public GetUserPropertyResponse entityToGetResponse(UserPropertyEntity entity) throws MapperException {
GetUserPropertyResponse getUserPropertyResponse = new GetUserPropertyResponse();
if ( entity != null ) {
getUserPropertyResponse.setBasic( jsonStrMapper.toJson( entity.getBasic() ) );
getUserPropertyResponse.setCredit( jsonStrMapper.toJson( entity.getCredit() ) );
getUserPropertyResponse.setGrowth( jsonStrMapper.toJson( entity.getGrowth() ) );
getUserPropertyResponse.setTag( jsonStrMapper.toJson( entity.getTag() ) );
if ( entity.getUserId() != null ) {
getUserPropertyResponse.setUserId( entity.getUserId() );
}
if ( entity.getScore() != null ) {
getUserPropertyResponse.setScore( entity.getScore() );
}
if ( entity.getLevel() != null ) {
getUserPropertyResponse.setLevel( entity.getLevel() );
}
getUserPropertyResponse.setResult( userPropertyEntityToResult( entity ) );
}
return getUserPropertyResponse;
}
}
但其实,我想要生成的代码是类似于下面这个样子的:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-01-26T11:24:54+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_272 (AdoptOpenJDK)"
)
@Component
public class UserPropertyMapperImpl implements UserPropertyMapper {
@Override
public GetUserPropertyResponse entityToGetResponse(UserPropertyEntity entity) throws MapperException {
GetUserPropertyResponse getUserPropertyResponse = new GetUserPropertyResponse();
if ( entity != null ) {
getUserPropertyResponse.setBasic( jsonStrMapper.toJson( entity.getBasic() ) );
getUserPropertyResponse.setCredit( jsonStrMapper.toJson( entity.getCredit() ) );
getUserPropertyResponse.setGrowth( jsonStrMapper.toJson( entity.getGrowth() ) );
getUserPropertyResponse.setTag( jsonStrMapper.toJson( entity.getTag() ) );
if ( entity.getUserId() != null ) {
getUserPropertyResponse.setUserId( entity.getUserId() );
}
if ( entity.getScore() != null ) {
getUserPropertyResponse.setScore( entity.getScore() );
}
if ( entity.getLevel() != null ) {
getUserPropertyResponse.setLevel( entity.getLevel() );
}
}
getUserPropertyResponse.setResult( userPropertyEntityToResult( entity ) );
return getUserPropertyResponse;
}
}
意思是:无论传入的对象是不是null,我都希望返回的对象中,result
属性是有值的。
2. 思路
这个问题该怎么解呢?
首先翻阅了一遍MapStruct的文档,通篇了解下来,没有发现特别有价值的说明。其中的NullValueMappingStrategy
,看起来也达不到我想要的效果:
public enum NullValueMappingStrategy {
/**
* If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no
* alternative strategy is configured globally, for given mapper or method.
*/
RETURN_NULL,
/**
* If {@code null} is passed to a mapping method, a default value will be returned. The value depends on the kind of
* the annotated method:
* <ul>
* <li>For bean mapping methods the target type will be instantiated and returned. Any properties of the target type
* which are mapped via {@link Mapping#expression()} or {@link Mapping#constant()} will be populated based on the
* given expression or constant. Note that expressions must be prepared to deal with {@code null} values in this
* case.</li>
* <li>For iterable mapping methods an empty collection will be returned.</li>
* <li>For map mapping methods an empty map will be returned.</li>
* </ul>
*/
RETURN_DEFAULT;
}
那既然无法从文档层面上找到解决方案,下一步正确的姿势就是要去看源码了。
然而,MapStruct的源码是作用在编译期的,常规的debug方案肯定没办法解决这个问题。这时候,就需要去debug源代码编译过程了。
3. 测试
那该怎么debug编译过程呢?
在网上搜了下相关内容,可以使用mvnDebug clean compile
命令启动远程调试过程。
所以,先在terminal上运行上述命令:
(base) ➜ insight-core git:(jingxuan_dev) ✗ mvnDebug clean compile
Preparing to execute Maven in debug mode
Listening for transport dt_socket at address: 8000
嗯,8000端口已经处于监听状态了。下面得想办法启动远端8000端口的服务。
在IDEA中,打开配置页面:
选择Remote模板,创建配置项:
按照下面的设置,增加设置,主要要设置8000端口:
点击图中的debug按钮:
这时,等在terminal中mvnDebug就开始跑了:
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ insight-core ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 94 source files to /Users/jingxuan/workspace/RED/insight/insight-core/target/classes
[INFO] /Users/jingxuan/workspace/RED/insight/insight-core/src/main/java/com/xiaohongshu/risk/platform/insight/schema/support/AbstractMapToEntityConverter.java: 某些输入文件使用了未经检查或不安全的操作。
[INFO] /Users/jingxuan/workspace/RED/insight/insight-core/src/main/java/com/xiaohongshu/risk/platform/insight/schema/support/AbstractMapToEntityConverter.java: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.278 s
[INFO] Finished at: 2021-01-26T12:14:49+08:00
[INFO] ------------------------------------------------------------------------
可是,它就这么直接跑完了,没断点啊!
3. 断点
首先,看下pom.xml中插件的配置:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg>
</compilerArgs>
</configuration>
</plugin>
这里面的mapstruct-processor
就是MapStruct用于生成映射源文件的包。但是,plugin中的包是不会引入到maven依赖中的。所以,我们还要手动将这个包加入到依赖中:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
这个时候,不出意外就能在依赖包中找到这个jar文件了:
找到处理类,增加断点。再次按照前面说的方法运行一遍,就能顺利开始debug了:
到这一步,只要能开始debug看源码,那距离解决问题就不远了。