基於IDEA Plugin外掛開發,擼一個DDD腳手架

小傅哥發表於2021-11-25

作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!?

  • 最近很感興趣結合 IDEA Plugin 開發能力,擴充套件各項功能。也基於此使用不同的案例,探索 IDEA Plugin 外掛開發技術。希望這樣的成體系學習和驗證總結,能給更多需要此技術的夥伴,帶來幫助。
  • 原始碼地址:https://github.com/fuzhengwei/CodeGuide#1-%E6%BA%90%E7%A0%81

一、前言

研發,要避免自嗨!

你做這個東西的價值是什麼?有競品調研嗎?能賦能業務嗎?那不已經有同類的了,你為什麼還自己造輪子?

你是不是也會被問到這樣的問題,甚至可能還有些頭疼。但做的時候挺嗨,研究技術嘛,還落地了,多刺激。不過要說價值,好像一時半會還體現不出來,能不能賦能業務就不更不一定了。

可誰又能保證以後不能呢,技術的點是一個個攻克嘗試的才有機會再深度學習後把這些內容連成一片,就像單說水、單說沙子、單說泥巴,好像並沒有啥用,但把它們湊到一塊再給把火,就燒成了磚,磚就碼成了牆,牆就蓋成房。

二、需求目的

我們這一章節把 freemarker 能力與 IDEA Plugin 外掛能力結合,開發一個DDD 腳手架 IDEA 外掛,可能你會想為什麼要把腳手架開發到外掛裡呢?還有不是已經有了成型的腳手架可以用嗎?

首先我們目前看到的腳手架基本都是網頁版的,也就是一次性建立工程使用,不過在我們實際使用的時候,還希望在工程建立過程中把資料庫、ES、Redis等生成對應的 ORM 程式碼,減少開發工作量。並且在使用的工程骨架的過程中,還希望可以隨著開發需要再次補充新的功能進去,這個時候網頁版的腳手架都不能很好的支援了。此外一些大廠都會自己的技術體系,完全是使用市面的腳手架基本很難滿足自身的需求,所以就需要有一個符合自己場景的腳手架了。

那麼,我們本章節就把腳手架的開發放到 IDEA 外掛開發中,一方面學習腳手架的建設,另外一方面學習如何改變工程嚮導,建立出自己需要的DDD結構腳手架。

三、案例開發

1. 工程結構

guide-idea-plugin-scaffolding
├── .gradle
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.guide.idea.plugin 
    │           ├── domain
    │           │     ├── model   
    │           │     │    └── ProjectConfigVO.java       
    │           │     └── service   
    │           │          ├── impl     
    │           │          │    └── ProjectGeneratorImpl.java  
    │           │          ├── AbstractProjectGenerator.java     
    │           │          ├── FreemarkerConfiguration.java      
    │           │          └── IProjectGenerator.java      
    │           ├── factory
    │           │      └── TemplateFactory.java  
    │           ├── infrastructure
    │           │     ├── DataSetting.java       
    │           │     ├── DataState.java  
    │           │     ├── ICONS.java      
    │           │     └── MsgBundle.java     
    │           ├── module  
    │           │     ├── DDDModuleBuilder.java    
    │           │     └── DDDModuleConfigStep.java         
    │           └── ui
    │                 ├── ProjectConfigUI.java  
    │                 └── ProjectConfigUI.form
    ├── resources
    │   ├── META-INF
    │   │   └── plugin.xml 
    │   └── template
    │       ├── pom.ftl
    │       └── yml.ftl 
    ├── build.gradle  
    └── gradle.properties

原始碼獲取:#公眾號:bugstack蟲洞棧 回覆:idea 即可下載全部 IDEA 外掛開發原始碼

在此 IDEA 外掛工程中,主要分為5塊區域:

  • domain:領域層,提供建立 DDD 模板工程的服務,其實這部分主要使用的就是 freemarker
  • factory:工廠層,提供工程建立模板,這一層的作用就是我們在 IDEA 中建立新工程的時候,可以新增上我們自己的內容,也就是建立出我們定義好的 DDD 工程結構。
  • infrastructure:基礎層,提供資料存放、圖片載入、資訊對映這些功能。
  • module:模組層,提供 DDD 模板工程的建立具體操作和步驟,也就是說我們建立工程的時候是一步步選擇的,你可以按需新增自己的步驟頁面,允許使用者選擇和新增自己需要的內容。比如你需要連庫、選擇表、新增工程所需要的技術棧等
  • ui:介面層,提供Swing 開發的 UI 介面,用於使用者圖形化選擇和建立。

2. UI 工程配置窗體

public class ProjectConfigUI {

    private JPanel mainPanel;
    private JTextField groupIdField;
    private JTextField artifactIdField;
    private JTextField versionField;
    private JTextField packageField;

}
  • 使用 Swing UI Designer 建立一個配置工廠資訊的 UI 窗體,通過這樣的方式建立可以直接拖拽。
  • 在這個 UI 窗體中我們主要需要;roupIdartifactIdversionpackage

3. 配置工程步驟建立

3.1 資料存放

cn.bugstack.guide.idea.plugin.infrastructure.DataSetting

@State(name = "DataSetting",storages = @Storage("plugin.xml"))
public class DataSetting implements PersistentStateComponent<DataState> {

    private DataState state = new DataState();

    public static DataSetting getInstance() {
        return ServiceManager.getService(DataSetting.class);
    }

    @Nullable
    @Override
    public DataState getState() {
        return state;
    }

    @Override
    public void loadState(@NotNull DataState state) {
        this.state = state;
    }

     public ProjectConfigVO getProjectConfig(){
        return state.getProjectConfigVO();
     }

}
  • 在基礎層提供資料存放的服務,把建立工程的配置資訊存放到服務中,這樣比較方便設定和獲取。

3.2 擴充套件步驟

cn.bugstack.guide.idea.plugin.module.DDDModuleConfigStep

public class DDDModuleConfigStep extends ModuleWizardStep {

    private ProjectConfigUI projectConfigUI;

    public DDDModuleConfigStep(ProjectConfigUI projectConfigUI) {
        this.projectConfigUI = projectConfigUI;
    }

    @Override
    public JComponent getComponent() {
        return projectConfigUI.getComponent();
    }

    @Override
    public boolean validate() throws ConfigurationException {
        // 獲取配置資訊,寫入到 DataSetting
        ProjectConfigVO projectConfig = DataSetting.getInstance().getProjectConfig();
        projectConfig.set_groupId(projectConfigUI.getGroupIdField().getText());
        projectConfig.set_artifactId(projectConfigUI.getArtifactIdField().getText());
        projectConfig.set_version(projectConfigUI.getVersionField().getText());
        projectConfig.set_package(projectConfigUI.getPackageField().getText());

        return super.validate();
    }

}
  • 繼承 ModuleWizardStep 開發一個自己需要的步驟,這個步驟就會出現到我們建立新的工程中。
  • 同時在重寫的 validate 方法中,把從工程配置 UI 窗體中獲取到資訊,寫入到資料配置檔案中。

3.3 配置步驟

cn.bugstack.guide.idea.plugin.module.DDDModuleBuilder

public class DDDModuleBuilder extends ModuleBuilder {

    private IProjectGenerator projectGenerator = new ProjectGeneratorImpl();

    @Override
    public Icon getNodeIcon() {
        return ICONS.SPRING_BOOT;
    }
    
    /**
     * 重寫 builderId 掛載自定義模板
     */
    @Nullable
    @Override
    public String getBuilderId() {
        return getClass().getName();
    }
    
    @Override
    public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, @NotNull ModulesProvider modulesProvider) {

        // 新增工程配置步驟,可以自己定義需要的步驟,如果有多個可以依次新增
        DDDModuleConfigStep moduleConfigStep = new DDDModuleConfigStep(new ProjectConfigUI());

        return new ModuleWizardStep[]{moduleConfigStep};
    }
}
  • 在 createWizardSteps 方法中,把我們已經建立好的 DDDModuleConfigStep 新增工程配置步驟,可以自己定義需要的步驟,如果有多個可以依次新增。
  • 同時需要注意,只有重寫了 getBuilderId() 方法後,你新增加的嚮導步驟才能生效。

4. 開發腳手架服務

cn.bugstack.guide.idea.plugin.domain.service.AbstractProjectGenerator

public abstract class AbstractProjectGenerator extends FreemarkerConfiguration implements IProjectGenerator {

    @Override
    public void doGenerator(Project project, String entryPath, ProjectConfigVO projectConfig) {

        // 1.建立工程主POM檔案
        generateProjectPOM(project, entryPath, projectConfig);

        // 2.建立四層架構
        generateProjectDDD(project, entryPath, projectConfig);

        // 3.建立 Application
        generateApplication(project, entryPath, projectConfig);

        // 4. 建立 Yml
        generateYml(project, entryPath, projectConfig);

        // 5. 建立 Common
        generateCommon(project, entryPath, projectConfig);
    }

}
  • 在 domain 領域層新增用於建立腳手架框架的 FreeMarker 服務,它是一款 模板引擎: 即一種基於模板和要改變的資料, 並用來生成輸出文字(HTML網頁,電子郵件,配置檔案,原始碼等)的通用工具。FreeMarker 線上手冊:http://freemarker.foofun.cn
  • 按照 DDD 工程結構,分層包括:application、domain、infrastructure、interfaces,那麼我們把這些建立過程抽象到模板方法中,具體交給子類來建立。

5. 呼叫腳手架服務

cn.bugstack.guide.idea.plugin.module.DDDModuleBuilder

public class DDDModuleBuilder extends ModuleBuilder {

    private IProjectGenerator projectGenerator = new ProjectGeneratorImpl();

    @Override
    public Icon getNodeIcon() {
        return ICONS.SPRING_BOOT;
    }

    @Override
    public void setupRootModel(@NotNull ModifiableRootModel rootModel) throws ConfigurationException {

        // 設定 JDK
        if (null != this.myJdk) {
            rootModel.setSdk(this.myJdk);
        } else {
            rootModel.inheritSdk();
        }

        // 生成工程路徑
        String path = FileUtil.toSystemIndependentName(Objects.requireNonNull(getContentEntryPath()));
        new File(path).mkdirs();
        VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
        rootModel.addContentEntry(virtualFile);

        Project project = rootModel.getProject();

        // 建立工程結構
        Runnable r = () -> new WriteCommandAction<VirtualFile>(project) {
            @Override
            protected void run(@NotNull Result<VirtualFile> result) throws Throwable {
                projectGenerator.doGenerator(project, getContentEntryPath(), DataSetting.getInstance().getProjectConfig());
            }
        }.execute();

    }

}
  • DDDModuleBuilder#setupRootModel 中,新增建立 DDD工程框架的服務,projectGenerator.doGenerator(project, getContentEntryPath(), DataSetting.getInstance().getProjectConfig());
  • 另外這裡需要用到 IDEA 提供的執行緒呼叫方法,new WriteCommandAction 才能正常建立。

6. 配置模板工程

6.1 模板工廠

cn.bugstack.guide.idea.plugin.factory.TemplateFactory

public class TemplateFactory extends ProjectTemplatesFactory {

    @NotNull
    @Override
    public String[] getGroups() {
        return new String[]{"DDD腳手架"};
    }

    @Override
    public Icon getGroupIcon(String group) {
        return ICONS.DDD;
    }

    @NotNull
    @Override
    public ProjectTemplate[] createTemplates(@Nullable String group, WizardContext context) {
        return new ProjectTemplate[]{new BuilderBasedTemplate(new DDDModuleBuilder())};
    }

}
  • 模板工廠的核心在於把我們用於建立 DDD 的步驟新增 createTemplates 方法中,這樣算把整個建立自定義腳手架工程的鏈路就串聯完成了。

6.2 檔案配置

plugin.xml

<idea-plugin>
    <id>cn.bugstack.guide.idea.plugin.guide-idea-plugin-scaffolding</id>
    <name>Scaffolding</name>
    <vendor email="184172133@qq.com" url="https://bugstack.cn">小傅哥</vendor>

    <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
         on how to target different products -->
    <depends>com.intellij.modules.platform</depends>

    <extensions defaultExtensionNs="com.intellij">
        <projectTemplatesFactory implementation="cn.bugstack.guide.idea.plugin.factory.TemplateFactory"/>
        <applicationService serviceImplementation="cn.bugstack.guide.idea.plugin.infrastructure.DataSetting"/>
    </extensions>

</idea-plugin>
  • 接下來還需要把我們建立的工程模板以及資料服務配置到 plugin.xml 中,這樣在外掛啟動的時候就可以把我們自己外掛啟動起來了。

四、測試驗證

  • 點選 Plugin 啟動 IDEA 外掛,之後建立工程如下:

  • 快拿去試試吧,啟動外掛,點選建立工程,傻瓜式點選,就可以建立出一個 DDD 工程結構了。

五、總結

  • 學習使用 IDEA Plugin 開發技術,改變建立工程嚮導,新增自己需要的工程建立模板,這樣就可以建立出一個 DDD 腳手架工程骨架了,接下來你還可以結合自己實際的業務場景新增自己需要的一些技術棧到腳手架中。
  • 如果你願意嘗試可以在工程建立中連結到資料庫,把資料庫中對應的表生成Java程式碼,這樣一些簡單的配置、查詢、對映,就不用自己動手寫了。
  • 在開發 DDD 腳手架的原始碼中還有一些細節過程,包括圖示的展示、文案的資訊、Freemarker的使用細節,這些你都可以在原始碼中學習並除錯驗證。

六、系列推薦

相關文章