基于IDEA Plugin插件开发,撸一个DDD脚手架
作者:小傅哥
博客: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 窗体中我们主要需要;
roupId、artifactId、version、package 
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的使用细节,这些你都可以在源码中学习并调试验证。
 
六、系列推荐
- 使用 Freemarker,创建 SpringBoot 脚手架
 - 发布Jar包到Maven中央仓库(为开发开源中间件做准备)
 - DDD 领域层决策规则树服务设计
 - 工作两三年了,整不明白架构图都画啥?
 - CodeGuide Github 仓库开源啦!
 
基于IDEA Plugin插件开发,撸一个DDD脚手架的更多相关文章
- 基于 getter 和 setter 撸一个简易的MVVM
		
Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...
 - C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架
		
C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...
 - 看了 Spring 官网脚手架真香,也撸一个 SpringBoot DDD 微服务的脚手架!
		
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么我们要去造轮子? 造轮子的核心目的,是为了解决通用共性问题的凝练和复用. 虽然 ...
 - 纯手工撸一个vue框架
		
前言 vue create 真的很方便,但是很多人欠缺的是手动撸一遍.有些人离开脚手架都不会开发了. Vue最简单的结构 步骤 搭建最基本的结构 打开空文件夹,通过 npm init 命令生成pack ...
 - 手撸一个SpringBoot-Starter
		
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
 - 手把手教你撸一个 Webpack Loader
		
文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该会很眼熟上面的图.正如它宣传的一样,webpack 能把左侧各种类型的文件(webpa ...
 - 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级
		
应用需求: 某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减.或改动).在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的 ...
 - 使用Java Socket手撸一个http服务器
		
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
 - 自己动手撸一个LinkedList
		
自己动手撸一个LinkedList 1. 原理 LinkedList是基于双链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历.因此,Linked ...
 
随机推荐
- js Promise用法
			
Promise 英文意思是 承诺的意思,是对将来的事情做了承诺, Promise 有三种状态, Pending 进行中或者等待中 Fulfilled 已成功 Rejected 已失败 Promise ...
 - pymysql基础
			
一,基本使用 倒入模块 import pymysql conn=pymysql.connect( host="数据库地址,本机是localhost,别的机器是ip", user=& ...
 - 初入CTF(封神榜第一关)
			
注:网址中的%20是空格的意思 1判断是否存在注入点 构造?id=1 and 1=1(不报错) 构造?id=1 and 1=2(报错或者不显示内容) 报错说明and后面的语句被识别 2判断回显字段的长 ...
 - Pytorch——torch.nn.Sequential()详解
			
参考:官方文档 源码 官方文档 nn.Sequential A sequential container. Modules will be added to it in the order th ...
 - 痞子衡嵌入式:超级下载算法RT-UFL v1.0在恩智浦MCUXpresso IDE下的使用
			
痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...
 - 【Spring】Spring重要类层次图
 - Java 是编译型语言还是解释型语言?
			
Java首先由编译器编译成.class类型的文件,这个是java自己类型的文件.然后在通过虚拟机(JVM)从.class文件中读一行解释执行一行.因此Java是一种半编译半解释的语言,理解这种意思即可 ...
 - 如何获取ISO8601定义的Work Week
			
工作中遇到一个需求,需要在打印标签的时候打印生产当天的工作周.工作周按照ISO-8601定义的标准计算.找到两种方案. Excel函数 C#代码 Excel函数 非常简单,调用一个Excel自带函数就 ...
 - 替换excel模板中的内容并使用JavaMail发送邮件
			
由于在公司工作,常年出差,每天都要以日报的形式向公司汇报当天的工作内容.而日报的内容大体上就只有当天工作的主要内容时变化的,其余的都是不变 的. 而我的电脑刚打开excel有点卡,因此决定使用Java ...
 - 攻防世界 杂项15.János-the-Ripper
			
下载附件并解压,我用的是WinHex打开,发现是PK开头,并且文件中包含一个flag.txt文件,应该就是我们所需要的flag. 把下载的附件改后缀为.zip,确实有我们需要的flag,打开后需要密码 ...