基於IDEA Plugin插件開發,擼一個DDD腳手架

作者:小傅哥
博客://bugstack.cn

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

  • 最近很感興趣結合 IDEA Plugin 開發能力,擴展各項功能。也基於此使用不同的案例,探索 IDEA Plugin 插件開發技術。希望這樣的成體系學習和驗證總結,能給更多需要此技術的夥伴,帶來幫助。
  • 源碼地址://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 在線手冊://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="[email protected]" url="//bugstack.cn">小傅哥</vendor>

    <!-- please see //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的使用細節,這些你都可以在源碼中學習並調試驗證。

六、系列推薦