0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

MapperStruct的使用教程

Android編程精選 ? 來源:稀土掘金技術(shù)社區(qū) ? 作者:DrLauPen ? 2022-10-11 11:15 ? 次閱讀

前言

相信絕大多數(shù)的業(yè)務(wù)開發(fā)同學(xué),日常的工作都離不開寫 getter、setter 方法。要么是將下游的 RPC 結(jié)果通過getter、setter 方法進(jìn)行獲取組裝。要么就是將自己系統(tǒng)內(nèi)部的處理結(jié)果通過 getter、setter 方法處理成前端所需要的 VO 對(duì)象。

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;
}

傳統(tǒng)的方法一般是采用硬編碼,將每個(gè)對(duì)象的值都逐一設(shè)值。當(dāng)然為了偷懶也會(huì)有采用一些 BeanUtil 簡(jiǎn)約代碼的方式:

publicUserInfoVOutilCopyItem(UserDTOuserDTO){
UserInfoVOuserInfoVO=newUserInfoVO();
//采用反射、內(nèi)省機(jī)制實(shí)現(xiàn)拷貝
BeanUtils.copyProperties(userDTO,userInfoVO);
returnuserInfoVO;
}

但是,像 BeanUtils 這類通過反射、內(nèi)省等實(shí)現(xiàn)的框架,在速度上會(huì)帶來比較嚴(yán)重的影響。尤其是對(duì)于一些大字段、大對(duì)象而言,這個(gè)速度的缺陷就會(huì)越明顯。針對(duì)速度這塊我還專門進(jìn)行了測(cè)試,對(duì)普通的 setter 方法、BeanUtils 的拷貝以及本次需要介紹的 mapperStruct 進(jìn)行了一次對(duì)比。得到的耗時(shí)結(jié)果如下所示:(具體的運(yùn)行代碼請(qǐng)見附錄)

運(yùn)行次數(shù) setter方法耗時(shí) BeanUtils拷貝耗時(shí) MapperStruct拷貝耗時(shí)
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)

以上單位均為毫微秒。括號(hào)內(nèi)的為當(dāng)前組件同 Setter 比較的比值。可以看到 BeanUtils 的拷貝耗時(shí)基本為 setter 方法的十倍、二十倍以上。而 MapperStruct 方法拷貝的耗時(shí),則與 setter 方法相近。由此可見,簡(jiǎn)單的 BeanUtils 確實(shí)會(huì)給服務(wù)的性能帶來很大的壓力。而 MapperStruct 拷貝則可以很好的解決這個(gè)問題。

下面我們就來介紹一下 MapperStruct 這個(gè)能夠很好提升我們代碼效率的工具。

使用教程

maven依賴

首先要導(dǎo)入 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 依賴后,我們首先來定義需要轉(zhuǎn)換的 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;
}

緊接著需要編寫相應(yīng)的mapper類,以便生成相應(yīng)的編譯類。

@Mapper
publicinterfaceInfoConverter{

InfoConverterINSTANT=Mappers.getMapper(InfoConverter.class);

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

需要注意的是,因?yàn)?DTO 中的 name 對(duì)應(yīng)的其實(shí)是 VO 中的 userName。因此需要在 converter 中顯式聲明。在編寫完對(duì)應(yīng)的文件之后,需要執(zhí)行 maven 的 complie 命令使得 IDE 編譯生成對(duì)應(yīng)的 Impl 對(duì)象。(自動(dòng)生成)

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

到此,mapperStruct 的接入就算是完成了~。我們就可以在我們的代碼中使用這個(gè)拷貝類了。

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

怎么樣,接入是不是很簡(jiǎn)單~

FAQ

1、接入項(xiàng)目時(shí),發(fā)現(xiàn)并沒有生成對(duì)應(yīng)的編譯對(duì)象class,這個(gè)是什么原因?

答:可能的原因有如下幾個(gè):

忘記編寫對(duì)應(yīng)的 @Mapper 注解,因而沒有生成

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

沒有執(zhí)行 maven 的 Compile,IDE 沒有進(jìn)行相應(yīng)編譯

2、接入項(xiàng)目后發(fā)現(xiàn),我項(xiàng)目?jī)?nèi)的 Lombok、@Data 注解不好使了,這怎么辦呢?

由于 Lombok 本身是對(duì) AST 進(jìn)行修改實(shí)現(xiàn)的,但是 mapStruct 在執(zhí)行的時(shí)候并不能檢測(cè)到 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}

更詳細(xì)的,mapperStruct 在官網(wǎng)中還提供了一個(gè)實(shí)現(xiàn) Lombok 及 mapStruct 同時(shí)并存的案例

「3、更多問題:」

歡迎查看MapStruct官網(wǎng)文檔,里面對(duì)各種問題都有更詳細(xì)的解釋及解答。

實(shí)現(xiàn)原理

在聊到 mapstruct 的實(shí)現(xiàn)原理之前,我們就需要先回憶一下 JAVA 代碼運(yùn)行的過程。大致的執(zhí)行生成的流程如下所示:

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

可以直觀的看到,如果我們想不通過編碼的方式對(duì)程序進(jìn)行修改增強(qiáng),可以考慮對(duì)抽象語法樹進(jìn)行相應(yīng)的修改。而mapstruct 也正是如此做的。具體的執(zhí)行邏輯如下所示:

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

為了實(shí)現(xiàn)該方法,mapstruct 基于JSR 269 實(shí)現(xiàn)了代碼。JSR 269 是 JDK 引進(jìn)的一種規(guī)范。有了它,能夠在編譯期處理注解,并且讀取、修改和添加抽象語法樹中的內(nèi)容。JSR 269 使用 Annotation Processor 在編譯期間處理注解,Annotation Processor 相當(dāng)于編譯器的一種插件,因此又稱為插入式注解處理。想要實(shí)現(xiàn) JSR 269,主要有以下幾個(gè)步驟:

繼承 AbstractProcessor 類,并且重寫 process 方法,在 process 方法中實(shí)現(xiàn)自己的注解處理邏輯。

在 META-INF/services 目錄下創(chuàng)建 javax.annotation.processing.Processor 文件注冊(cè)自己實(shí)現(xiàn)的 Annotation Processor。

通過實(shí)現(xiàn)AbstractProcessor,在程序進(jìn)行 compile 的時(shí)候,會(huì)對(duì)相應(yīng)的 AST 進(jìn)行修改。從而達(dá)到目的。

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);

//此處會(huì)調(diào)用到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();
}
}

關(guān)鍵代碼,在mapstruct-processor包中,有個(gè)對(duì)應(yīng)的類MappingProcessor繼承了 AbstractProcessor,并實(shí)現(xiàn)其 process 方法。通過對(duì) AST 進(jìn)行相應(yīng)的代碼增強(qiáng),從而實(shí)現(xiàn)對(duì)最終編譯的對(duì)象進(jìn)行修改的方法。

@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;
}
}

「如何斷點(diǎn)調(diào)試:」

因?yàn)檫@個(gè)注解處理器是在解析->編譯的過程完成,跟普通的 jar 包調(diào)試不太一樣,maven 框架為我們提供了調(diào)試入口,需要借助 maven 才能實(shí)現(xiàn) debug。所以需要在編譯過程打開 debug 才可調(diào)試。

在項(xiàng)目的 pom 文件所在目錄執(zhí)行 mvnDebug compile

接著用 idea 打開項(xiàng)目,添加一個(gè) remote,端口為 8000

打上斷點(diǎn),debug 運(yùn)行 remote 即可調(diào)試。

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

附錄

測(cè)試代碼如下,采用Spock框架 + JAVA代碼 實(shí)現(xiàn)。Spock框架作為當(dāng)前最火熱的測(cè)試框架,你值得學(xué)習(xí)一下。Spock框架初體驗(yàn):更優(yōu)雅地寫好你的單元測(cè)試

//@Resource
@Shared
MapperStructServicemapperStructService

defsetupSpec(){
mapperStructService=newMapperStructService()
}

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

when:"調(diào)用方法"
//傳統(tǒng)的getter、setter拷貝
longstartTime=System.nanoTime();
UserInfoVOoldRes=mapperStructService.originalCopyItem(dto,times)
DurationoriginalWasteTime=Duration.ofNanos(System.nanoTime()-startTime);

//采用工具實(shí)現(xiàn)反射類的拷貝
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:"校驗(yàn)數(shù)據(jù)"
println("times="+times)
println("原始拷貝的消耗時(shí)間為:"+originalWasteTime.getNano())
println("BeanUtils拷貝的消耗時(shí)間為:"+utilWasteTime.getNano())
println("mapStruct拷貝的消耗時(shí)間為:"+mapStructWasteTime.getNano())
println()

where:"比較不同次數(shù)調(diào)用的耗時(shí)"
times||ignore
1||null
10||null
100||null
1000||null
}

測(cè)試的Service如下所示:

publicclassMapperStructService{

publicUserInfoVOnewCopyItem(UserDTOuserDTO,inttimes){
UserInfoVOuserInfoVO=newUserInfoVO();
for(inti=0;i

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • RPC
    RPC
    +關(guān)注

    關(guān)注

    0

    文章

    111

    瀏覽量

    11495
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4728

    瀏覽量

    68250

原文標(biāo)題:用了這個(gè)工具后,再也不寫 getter、setter 了!

文章出處:【微信號(hào):AndroidPush,微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論