0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MapperStruct的使用教程

Android编程精选 来源:稀土掘金技术社区 作者:DrLauPen 2022-10-11 11:15 次阅读

前言

相信绝大多数的业务开发同学,日常的工作都离不开写 getter、setter 方法。要么是将下游的 RPC 结果通过getter、setter 方法进行获取组装。要么就是将自己系统内部的处理结果通过 getter、setter 方法处理成前端所需要的 VO 对象。

publicUserInfoVOoriginalCopyItem(UserDTOuserDTO){
UserInfoVOuserInfoVO=newUserInfoVO();
userInfoVO.setUserName(userDTO.getName());
userInfoVO.setAge(userDTO.getAge());
userInfoVO.setBirthday(userDTO.getBirthday());
userInfoVO.setIdCard(userDTO.getIdCard());
userInfoVO.setGender(userDTO.getGender());
userInfoVO.setIsMarried(userDTO.getIsMarried());
userInfoVO.setPhoneNumber(userDTO.getPhoneNumber());
userInfoVO.setAddress(userDTO.getAddress());
returnuserInfoVO;
}

传统的方法一般是采用硬编码,将每个对象的值都逐一设值。当然为了偷懒也会有采用一些 BeanUtil 简约代码的方式:

publicUserInfoVOutilCopyItem(UserDTOuserDTO){
UserInfoVOuserInfoVO=newUserInfoVO();
//采用反射、内省机制实现拷贝
BeanUtils.copyProperties(userDTO,userInfoVO);
returnuserInfoVO;
}

但是,像 BeanUtils 这类通过反射、内省等实现的框架,在速度上会带来比较严重的影响。尤其是对于一些大字段、大对象而言,这个速度的缺陷就会越明显。针对速度这块我还专门进行了测试,对普通的 setter 方法、BeanUtils 的拷贝以及本次需要介绍的 mapperStruct 进行了一次对比。得到的耗时结果如下所示:(具体的运行代码请见附录)

运行次数 setter方法耗时 BeanUtils拷贝耗时 MapperStruct拷贝耗时
1 2921528(1) 3973292(1.36) 2989942(1.023)
10 2362724(1) 66402953(28.10) 3348099(1.417)
100 2500452(1) 71741323(28.69) 2120820(0.848)
1000 3187151(1) 157925125(49.55) 5456290(1.711)
10000 5722147(1) 300814054(52.57) 5229080(0.913)
100000 19324227(1) 244625923(12.65) 12932441(0.669)

以上单位均为毫微秒。括号内的为当前组件同 Setter 比较的比值。可以看到 BeanUtils 的拷贝耗时基本为 setter 方法的十倍、二十倍以上。而 MapperStruct 方法拷贝的耗时,则与 setter 方法相近。由此可见,简单的 BeanUtils 确实会给服务的性能带来很大的压力。而 MapperStruct 拷贝则可以很好的解决这个问题。

下面我们就来介绍一下 MapperStruct 这个能够很好提升我们代码效率的工具。

使用教程

maven依赖

首先要导入 mapStruct 的 maven 依赖,这里我们选择最新的版本 1.5.0.RC1。

...

1.5.0.RC1

...

//mapStructmaven依赖


org.mapstruct
mapstruct
${org.mapstruct.version}


...

//编译的组件需要配置



org.apache.maven.plugins
maven-compiler-plugin
3.8.1

1.8 
1.8 


org.mapstruct
mapstruct-processor
${org.mapstruct.version}

 





在引入 maven 依赖后,我们首先来定义需要转换的 DTO 及 VO 信息,主要包含的信息是名字、年龄、生日、性别等信息。

@Data
publicclassUserDTO{
privateStringname;

privateintage;

privateDatebirthday;

//1-男0-女
privateintgender;

privateStringidCard;

privateStringphoneNumber;

privateStringaddress;

privateBooleanisMarried;
}
@Data
publicclassUserInfoVO{
privateStringuserName;

privateintage;

privateDatebirthday;

//1-男0-女
privateintgender;

privateStringidCard;

privateStringphoneNumber;

privateStringaddress;

privateBooleanisMarried;
}

紧接着需要编写相应的mapper类,以便生成相应的编译类。

@Mapper
publicinterfaceInfoConverter{

InfoConverterINSTANT=Mappers.getMapper(InfoConverter.class);

@Mappings({
@Mapping(source="name",target="userName")
})
UserInfoVOconvert(UserDTOuserDto);
}

需要注意的是,因为 DTO 中的 name 对应的其实是 VO 中的 userName。因此需要在 converter 中显式声明。在编写完对应的文件之后,需要执行 maven 的 complie 命令使得 IDE 编译生成对应的 Impl 对象。(自动生成)

148c8974-3cc7-11ed-9e49-dac502259ad0.jpg

到此,mapperStruct 的接入就算是完成了~。我们就可以在我们的代码中使用这个拷贝类了。

publicUserInfoVOnewCopyItem(UserDTOuserDTO,inttimes){
UserInfoVOuserInfoVO=newUserInfoVO();
userInfoVO=InfoConverter.INSTANT.convert(userDTO);
returnuserInfoVO;
}

怎么样,接入是不是很简单~

FAQ

1、接入项目时,发现并没有生成对应的编译对象class,这个是什么原因?

答:可能的原因有如下几个:

忘记编写对应的 @Mapper 注解,因而没有生成

没有配置上述提及的插件 maven-compiler-plugin

没有执行 maven 的 Compile,IDE 没有进行相应编译

2、接入项目后发现,我项目内的 Lombok、@Data 注解不好使了,这怎么办呢?

由于 Lombok 本身是对 AST 进行修改实现的,但是 mapStruct 在执行的时候并不能检测到 Lombok 所做的修改,因此需要额外的引入 maven 依赖lombok-mapstruct-binding。

......
1.5.0.RC1
0.2.0
1.18.20
......

......

org.mapstruct
mapstruct
${org.mapstruct.version}


org.projectlombok
lombok-mapstruct-binding
${lombok-mapstruct-binding.version}


org.projectlombok
lombok
${lombok.version}

更详细的,mapperStruct 在官网中还提供了一个实现 Lombok 及 mapStruct 同时并存的案例

「3、更多问题:」

欢迎查看MapStruct官网文档,里面对各种问题都有更详细的解释及解答。

实现原理

在聊到 mapstruct 的实现原理之前,我们就需要先回忆一下 JAVA 代码运行的过程。大致的执行生成的流程如下所示:

149b0828-3cc7-11ed-9e49-dac502259ad0.png

可以直观的看到,如果我们想不通过编码的方式对程序进行修改增强,可以考虑对抽象语法树进行相应的修改。而mapstruct 也正是如此做的。具体的执行逻辑如下所示:

14b7d1a6-3cc7-11ed-9e49-dac502259ad0.jpg

为了实现该方法,mapstruct 基于JSR 269 实现了代码。JSR 269 是 JDK 引进的一种规范。有了它,能够在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269 使用 Annotation Processor 在编译期间处理注解,Annotation Processor 相当于编译器的一种插件,因此又称为插入式注解处理。想要实现 JSR 269,主要有以下几个步骤:

继承 AbstractProcessor 类,并且重写 process 方法,在 process 方法中实现自己的注解处理逻辑。

在 META-INF/services 目录下创建 javax.annotation.processing.Processor 文件注册自己实现的 Annotation Processor。

通过实现AbstractProcessor,在程序进行 compile 的时候,会对相应的 AST 进行修改。从而达到目的。

publicvoidcompile(ListsourceFileObjects,
Listclassnames,
Iterableprocessors)
{
if(processors!=null&&processors.iterator().hasNext())
explicitAnnotationProcessingRequested=true;
//asaJavaCompilercanonlybeusedonce,throwanexceptionif
//ithasbeenusedbefore.
if(hasBeenUsed)
thrownewAssertionError("attempttoreuseJavaCompiler");
hasBeenUsed=true;

//forciblysettheequivalentof-Xlint:-options,sothatnofurther
//warningsaboutcommandlineoptionsaregeneratedfromthispointon
options.put(XLINT_CUSTOM.text+"-"+LintCategory.OPTIONS.option,"true");
options.remove(XLINT_CUSTOM.text+LintCategory.OPTIONS.option);

start_msec=now();

try{
initProcessAnnotations(processors);

//此处会调用到mapStruct中的processor类的方法.
delegateCompiler=
processAnnotations(
enterTrees(stopIfError(CompileState.PARSE,parseFiles(sourceFileObjects))),
classnames);

delegateCompiler.compile2();
delegateCompiler.close();
elapsed_msec=delegateCompiler.elapsed_msec;
}catch(Abortex){
if(devVerbose)
ex.printStackTrace(System.err);
}finally{
if(procEnvImpl!=null)
procEnvImpl.close();
}
}

关键代码,在mapstruct-processor包中,有个对应的类MappingProcessor继承了 AbstractProcessor,并实现其 process 方法。通过对 AST 进行相应的代码增强,从而实现对最终编译的对象进行修改的方法。

@SupportedAnnotationTypes({"org.mapstruct.Mapper"})
@SupportedOptions({"mapstruct.suppressGeneratorTimestamp","mapstruct.suppressGeneratorVersionInfoComment","mapstruct.unmappedTargetPolicy","mapstruct.unmappedSourcePolicy","mapstruct.defaultComponentModel","mapstruct.defaultInjectionStrategy","mapstruct.disableBuilders","mapstruct.verbose"})
publicclassMappingProcessorextendsAbstractProcessor{
publicbooleanprocess(Setannotations,RoundEnvironmentroundEnvironment){
if(!roundEnvironment.processingOver()){
RoundContextroundContext=newRoundContext(this.annotationProcessorContext);
SetdeferredMappers=this.getAndResetDeferredMappers();
this.processMapperElements(deferredMappers,roundContext);
Setmappers=this.getMappers(annotations,roundEnvironment);
this.processMapperElements(mappers,roundContext);
}elseif(!this.deferredMappers.isEmpty()){
Iteratorvar8=this.deferredMappers.iterator();

while(var8.hasNext()){
MappingProcessor.DeferredMapperdeferredMapper=(MappingProcessor.DeferredMapper)var8.next();
TypeElementdeferredMapperElement=deferredMapper.deferredMapperElement;
ElementerroneousElement=deferredMapper.erroneousElement;
StringerroneousElementName;
if(erroneousElementinstanceofQualifiedNameable){
erroneousElementName=((QualifiedNameable)erroneousElement).getQualifiedName().toString();
}else{
erroneousElementName=erroneousElement!=null?erroneousElement.getSimpleName().toString():null;
}

deferredMapperElement=this.annotationProcessorContext.getElementUtils().getTypeElement(deferredMapperElement.getQualifiedName());
this.processingEnv.getMessager().printMessage(Kind.ERROR,"Noimplementationwascreatedfor"+deferredMapperElement.getSimpleName()+"duetohavingaproblemintheerroneouselement"+erroneousElementName+".Hint:thisoftenmeansthatsomeotherannotationprocessorwassupposedtoprocesstheerroneouselement.YoucanalsoenableMapStructverbosemodebysetting-Amapstruct.verbose=trueasacompilationargument.",deferredMapperElement);
}
}

returnfalse;
}
}

「如何断点调试:」

因为这个注解处理器是在解析->编译的过程完成,跟普通的 jar 包调试不太一样,maven 框架为我们提供了调试入口,需要借助 maven 才能实现 debug。所以需要在编译过程打开 debug 才可调试。

在项目的 pom 文件所在目录执行 mvnDebug compile

接着用 idea 打开项目,添加一个 remote,端口为 8000

打上断点,debug 运行 remote 即可调试。

14cd29f2-3cc7-11ed-9e49-dac502259ad0.jpg

附录

测试代码如下,采用Spock框架 + JAVA代码 实现。Spock框架作为当前最火热的测试框架,你值得学习一下。Spock框架初体验:更优雅地写好你的单元测试

//@Resource
@Shared
MapperStructServicemapperStructService

defsetupSpec(){
mapperStructService=newMapperStructService()
}

@Unroll
def"testmapperStructTesttimes=#times"(){
given:"初始化数据"
UserDTOdto=newUserDTO(name:"笑傲菌",age:20,idCard:"1234",
phoneNumber:"18211932334",address:"北京天安门",gender:1,
birthday:newDate(),isMarried:false)

when:"调用方法"
//传统的getter、setter拷贝
longstartTime=System.nanoTime();
UserInfoVOoldRes=mapperStructService.originalCopyItem(dto,times)
DurationoriginalWasteTime=Duration.ofNanos(System.nanoTime()-startTime);

//采用工具实现反射类的拷贝
longstartTime1=System.nanoTime();
UserInfoVOutilRes=mapperStructService.utilCopyItem(dto,times)
DurationutilWasteTime=Duration.ofNanos(System.nanoTime()-startTime1);

longstartTime2=System.nanoTime();
UserInfoVOmapStructRes=mapperStructService.newCopyItem(dto,times)
DurationmapStructWasteTime=Duration.ofNanos(System.nanoTime()-startTime2);

then:"校验数据"
println("times="+times)
println("原始拷贝的消耗时间为:"+originalWasteTime.getNano())
println("BeanUtils拷贝的消耗时间为:"+utilWasteTime.getNano())
println("mapStruct拷贝的消耗时间为:"+mapStructWasteTime.getNano())
println()

where:"比较不同次数调用的耗时"
times||ignore
1||null
10||null
100||null
1000||null
}

测试的Service如下所示:

publicclassMapperStructService{

publicUserInfoVOnewCopyItem(UserDTOuserDTO,inttimes){
UserInfoVOuserInfoVO=newUserInfoVO();
for(inti=0;i< times; i++) {
            userInfoVO = InfoConverter.INSTANT.convert(userDTO);
        }
        return userInfoVO;
    }

    public UserInfoVO originalCopyItem(UserDTO userDTO, int times) {
        UserInfoVO userInfoVO = new UserInfoVO();
        for (int i = 0; i < times; i++) {
            userInfoVO.setUserName(userDTO.getName());
            userInfoVO.setAge(userDTO.getAge());
            userInfoVO.setBirthday(userDTO.getBirthday());
            userInfoVO.setIdCard(userDTO.getIdCard());
            userInfoVO.setGender(userDTO.getGender());
            userInfoVO.setIsMarried(userDTO.getIsMarried());
            userInfoVO.setPhoneNumber(userDTO.getPhoneNumber());
            userInfoVO.setAddress(userDTO.getAddress());
        }
        return userInfoVO;
    }

    public UserInfoVO utilCopyItem(UserDTO userDTO, int times) {
        UserInfoVO userInfoVO = new UserInfoVO();
        for (int i = 0; i < times; i++) {
            BeanUtils.copyProperties(userDTO, userInfoVO);
        }
        return userInfoVO;
    }
}

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RPC
    RPC
    +关注

    关注

    0

    文章

    102

    浏览量

    11424
  • 代码
    +关注

    关注

    30

    文章

    4556

    浏览量

    66814

原文标题:用了这个工具后,再也不写 getter、setter 了!

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论