作者:小傅哥

博客: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的使用细节,这些你都可以在源码中学习并调试验证。

六、系列推荐

基于IDEA Plugin插件开发,撸一个DDD脚手架的更多相关文章

  1. 基于 getter 和 setter 撸一个简易的MVVM

    Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...

  2. C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架

    C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...

  3. 看了 Spring 官网脚手架真香,也撸一个 SpringBoot DDD 微服务的脚手架!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么我们要去造轮子? 造轮子的核心目的,是为了解决通用共性问题的凝练和复用. 虽然 ...

  4. 纯手工撸一个vue框架

    前言 vue create 真的很方便,但是很多人欠缺的是手动撸一遍.有些人离开脚手架都不会开发了. Vue最简单的结构 步骤 搭建最基本的结构 打开空文件夹,通过 npm init 命令生成pack ...

  5. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

  6. 手把手教你撸一个 Webpack Loader

    文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该会很眼熟上面的图.正如它宣传的一样,webpack 能把左侧各种类型的文件(webpa ...

  7. 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级

    应用需求: 某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减.或改动).在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的 ...

  8. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  9. 自己动手撸一个LinkedList

    自己动手撸一个LinkedList 1. 原理 LinkedList是基于双链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历.因此,Linked ...

随机推荐

  1. 活动回顾|ShardingSphere X openGauss,将会产生怎样的化学反应?

    "ShardingSphere 作为 openGauss 生态的开源分布式数据库解决方案,将持续助力于 openGauss,满足千行百业广大客户分布式场景需求." 5月29日,由  ...

  2. React实现组件全屏化

    介绍 本文基于React+antd,给大家演示一个完整的全屏demo. 起因是开发今天给我提了一个sql编辑器输入框比较小,不支持放大,不太方便.希望能够全屏显示,联想到自己以后可能也会需要,便研究并 ...

  3. 电脑(windows)端口被占用如何解决

    问题: 今天在启动项目的时候,控制台提示"8080端口被占用",此时我并没有启动其他项目.那么8080端口被占用解决方法如下: 1.点击左下角"开始",在搜索框 ...

  4. 树莓派4B学习札记

    防静电 树莓派比较容易被静电损坏,要做好以下预防措施 使用的时候不要用手去触摸PCB和针脚!特别是上电之后! 拿板卡的时候,要习惯性拿板卡的边缘 勤洗手,勤摸墙壁,释放身上的静电 系统安装 8GB以上 ...

  5. 笨方法学python中执行argv提示ValueError: not enough values to unpack (expected 4, got 1)

    解决方法:选择Terminal中输入执行ex13.py 1 2 3 执行结果如下图

  6. 初学Python-day13 文件处理1

    IO操作 一.os模块 作用:包含了操作系统的基本功能,提供了非常丰富的用来处理文件和目录的函数或方法. 1.属性 函数名 函数说明 name 获取操作系统的类型 uname 获取操作系统的信息(li ...

  7. 【UE4 调试】提升UE4源码版本Setup下载速度

    更改setup.bat部分参数

  8. relativeLayout相对布局的嵌套在py中的引用

    from kivy.app import App from kivy.uix.button import Button from kivy.uix.relativelayout import Rela ...

  9. Linux该如何学习新手入门遇到问题又该如何解决

    本节旨在介绍对于初学者如何学习 Linux 的建议.如果你已经确定对 Linux 产生了兴趣,那么接下来我们介绍一下学习 Linux 的方法. 如何去学习 学习大多类似庖丁解牛,对事物的认识一般都是由 ...

  10. 最小最大堆min-max Heap

    (1)由性质5只能插在奇数层,即根节点处,7下沉到右堆的min level,10下沉到max level,插入后满足min-max heap性质,很容易画出: (2)由性质80也是向右堆插入,且插入到 ...