![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL3MzMxYTO4cDMy0iMzEzMwEDN0EzMyITMxIDMy0iMwIDMzAjMvwlMxEjMwIzLcJDMyAzMwIzLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
讓人頭疼的對象轉換
頭炸,
po2vo
、
vo2do
do2dto
,一堆對象屬性,取出來塞進來。要不是為了 DDD 架構下的各個分層防腐,真想一竿子怼下去。
那上
BeanUtils.copyProperties
呀,其實對象轉換不隻這個方法,還有同類的12種手段,但綜合來看還是
MapStruct
在編譯期生成x.set(y.get)代碼的最終效果最好,整體壓測資料如下:
-
是大家代碼裡最常出現的工具類,但隻要你不把它用錯成BeanUtils.copyProperties
包下的,而是使用 Spring 提供的,就基本還不會對性能造成多大影響。Apache
- 但如果說性能更好,可替代手動
的,還是get、set
更好用,因為它本身就是在編譯期生成MapStruct
代碼,和我們寫get、set
一樣。get、set
- 其他一些元件包主要基于
AOP
ASM
,的技術手段實作的,是以也會有相應的性能損耗。CGlib
咋辦? 給每一個轉換對象屬性的操作都寫一個
MapStruct
嗎?也不合适呀,有些就是方法中很簡單的操作一下,寫寫代碼就能搞定,問題就是懶的寫,一多了還容易寫錯。别提 BeanUtils.copyProperties 有時候确定有性能問題,從編碼上還看不出來屬性的添加和減少
是以 我要寫個 IDEA Plugin 解決這個問題,目的就一個,通過 IDEA 插件開發能力,定義到我需要轉換屬性的2個對象,把2個對象的轉換代碼自動生成出來,并織入到我的對象定位位置上。
設計一個插件
我是這麼思考的:在 IDEA 開發工程代碼中,在需要轉換的2個對象間,複制第一個對象和屬性,再把光标定位到轉換對象上,接下來我給它提供個按鈕或者快捷鍵,一點就把所有轉換代碼生成出來,這樣不就解決了需要手寫的問題了嗎,效果如下:
1. 工程結構
vo2dto
├── .gradle
└── src
├── main
│ └── java
│ └── cn.bugstack.guide.idea.plugin
│ ├── action
│ │ └── Vo2DtoGenerateAction.java
│ ├── application
│ │ └── IGenerateVo2Dto.java
│ ├── domain
│ │ ├── model
│ │ │ ├── GenerateContext.java
│ │ │ ├── GetObjConfigDO.java
│ │ │ └── SetObjConfigDO.java
│ │ └── service
│ │ ├── impl
│ │ │ └── GenerateVo2DtoImpl.java
│ │ └── AbstractGenerateVo2Dto.java
│ └── infrastructure
│ └── Utils.java
├── resources
│ └── META-INF
│ └── plugin.xml
├── build.gradle
└── gradle.properties
源碼擷取:https://github.com/fuzhengwei/vo2dto -
歡迎送出 issue、PR 共同維護
在此 IDEA 插件工程中,主要分為4塊區域:
- action:提供菜單欄窗體,在插件中我們把這個菜單欄配置到
下,也就是通常你生成 get、set、constructor 方法的地方。Generate
- application:應用層定義接口,這裡定義了一個用于生成代碼并織入到錨點的方法接口。
- domian:領域層專門處理代碼的生成和織入動作,這一層把代碼的中錨點位置擷取、剪切闆資訊複制、應用上下文、類中get、set的解析,以及最終把生成代碼織入到錨點後的操作。
- infrastructure:在基礎層提供了工具類,用于擷取剪切闆資訊和錨點位置判斷等操作。
2. 織入代碼接口
cn.bugstack.guide.idea.plugin.application.IGenerateVo2Dto
public interface IGenerateVo2Dto {
void doGenerate(Project project, DataContext dataContext);
}
- 定義接口其實非常重要的一步,因為這樣一步就把生成的标準定義下來了,所有的生成動作都要從這個接口發起。學習源碼也一樣,你要找到一個核心的入口點,才能更好的開始學習
3. 定義模闆方法
因為生成代碼并織入錨點位置的操作,整個來看其實也是一套流程操作,因為在這個過程需要;擷取上下文資訊(也就是工程對象)、給目前錨點位置的類提取 set 方法集合、之後在給
Ctrl+C
剪切闆上的資訊讀取出來提取 get 方法集合,第四步把set、get進行組合并織入代碼到錨點位置。整體過程如下:
- 那麼在使用模闆方法後,就可以非常容易的把寫在一個類裡的成片的代碼按照職責進行拆分。
- 同時因為有了模闆的定義,也就定義出了整個一套标準流程,在流程規範下執行代碼,後續再補充邏輯疊代功能也會更加容易。
4. 代碼織入錨點
關于代碼織入錨點前,我們在模闆類中定義的方法,需要實作接口進行處理,重點包括:
- 通過
CommonDataKeys.EDITOR.getData(dataContext)
封裝 GenerateContext 對象上下文資訊,也就是一些類、錨點位置、文檔編輯的對象。CommonDataKeys.PSI_ELEMENT.getData(dataContext)
- 通過 PsiClass 擷取光标位置對應的 Class 類資訊,在通過
讀取對象方法,把 set 方法過濾出來,封裝到集合中。psiClass.getMethods()
-
擷取剪切闆資訊,也就是你在錨點位置給對象生成Toolkit.getDefaultToolkit().getSystemClipboard()
時,複制的 Y y 對象,并開始提取 get 方法,同樣封裝到集合中。x.set(y.get)
- 那麼最後就是代碼的組裝和織入動作了,這部分我們的代碼如下;
cn.bugstack.guide.idea.plugin.domain.service.impl.GenerateVo2DtoImpl
@Override
protected void weavingSetGetCode(GenerateContext generateContext, SetObjConfigDO setObjConfigDO, GetObjConfigDO getObjConfigDO) {
Application application = ApplicationManager.getApplication();
// 擷取空格位置長度
int distance = Utils.getWordStartOffset(generateContext.getEditorText(), generateContext.getOffset()) - generateContext.getStartOffset();
application.runWriteAction(() -> {
StringBuilder blankSpace = new StringBuilder();
for (int i = 0; i < distance; i++) {
blankSpace.append(" ");
}
int lineNumberCurrent = generateContext.getDocument().getLineNumber(generateContext.getOffset()) + 1;
List<String> setMtdList = setObjConfigDO.getParamList();
for (String param : setMtdList) {
int lineStartOffset = generateContext.getDocument().getLineStartOffset(lineNumberCurrent++);
WriteCommandAction.runWriteCommandAction(generateContext.getProject(), () -> {
generateContext.getDocument().insertString(lineStartOffset, blankSpace + setObjConfigDO.getClazzParamName() + "." + setObjConfigDO.getParamMtdMap().get(param) + "(" + (null == getObjConfigDO.getParamMtdMap().get(param) ? "" : getObjConfigDO.getClazzParam() + "." + getObjConfigDO.getParamMtdMap().get(param) + "()") + ");\n");
generateContext.getEditor().getCaretModel().moveToOffset(lineStartOffset + 2);
generateContext.getEditor().getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
});
}
});
}
- 織入代碼的流程動作,主要是對set方法集合進行周遊,把對應的
x.set(y.get)
到具體的位置和代碼。document.insertString
- 最終所有生成的代碼方法織入完成,即完成了整個
的過程。x.set(y.get)
5. 配置菜單入口
plugin.xml
<actions>
<!-- Add your actions here -->
<action id="Vo2DtoGenerateAction" class="cn.bugstack.guide.idea.plugin.action.Vo2DtoGenerateAction"
text="Vo2Dto - 小傅哥" description="Vo2Dto generate util" icon="/icons/logo.png">
<add-to-group group-id="GenerateGroup" anchor="last"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift K"/>
</action>
</actions>
- 這次我們給生成
代碼的操作加個快捷鍵,可以讓我們更加友善的進行操作。x.set(y.get)
安裝使用驗證
- 由于釋出插件需要到 https://plugins.jetbrains.com/ 并等待稽核,是以可以在 release 包下載下傳:https://github.com/fuzhengwei/vo2dto/releases/tag/v2.2.2 下載下傳後手動安裝即可。
接下來你就可以 So Easy 的轉換對象了,操作如下:
- 複制你需要被轉換的對象,因為複制以後就可以被插件擷取到剪切闆資訊了,也就能提取到get方法集合。
- 把滑鼠定義到需要轉換設定值的對象,之後滑鼠右鍵,選擇
->Generate
Vo2Dto - 小傅哥
1. 複制對象
2. 生成對象
3. 最終效果
- 最終你就可以看到已經把你全部的對象轉換,自動生成出來代碼了,是不是很香。
- 如果你直接使用快捷鍵
也是可以自動生成的。Ctrl + Shift + K
拿去用用吧,最好再給提一些建議,送出issue、送出PR,都非常的歡迎!
公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!