手撸 SpringBoot 脚手架:用 FreeMarker 模板引擎构建企业级工程框架
文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载作者小傅哥脚手架并非只有 Spring 官网和阿里云才能提供在大型互联网公司中通用脚手架往往无法融入公司自研的 RPC、数据库路由、注册中心、分布式任务、MQ 队列等组件此时就需要结合模板引擎自行开发一套符合公司技术栈的统一脚手架。本文以 CodeGuide 仓库中的《不重复造轮子只是骗小孩子的教你手撸 SpringBoot 脚手架》为核心完整演示如何使用 FreeMarkerFTL 模板从零构建一个 SpringBoot 脚手架工程并借助仓库中后续的 DDD 脚手架演进案例讲解脚手架从生成基础工程到支撑 DDD 四层架构与分布式技术栈的完整落地路径。一、为什么我们要去造轮子造轮子的核心目的是为了解决通用共性问题的凝练和复用。虽然市面上已经有了大量成熟稳定用于支撑系统建设的轮子也就是服务、框架、组件、工具等但对于一些较大型的公司来说这些轮子可能并不一定能很好的支撑起系统需要承载的服务体量这个时候就需要自建一些轮子。而提倡的不重复造轮子一般用在以官网推出的核心轮子上是适合的比如 SpringBoot、Netty、HBase 等。但对于一些特殊场景的解决方案工具型组件通常是没有完全符合的轮子的就像 SpringBoot 脚手架。其实每个较大型的公司都会有很多同类技术服务的组件例如 RPC、数据库路由、注册中心、分布式任务、MQ 队列消息等而这时候脚手架的开发就需要适配这些组件搭建出符合自己公司技术栈实现需要的系统架构。这不同于一些较小的互联网公司可以完全使用 SpringBoot 提供的一整套解决方案。另外造轮子是个人技术沉淀、也是薪资待遇的积累什么场景还能造轮子用于架构基建下的所有模块都可以成为轮子通常我们都是在这些场景下建设需要的轮子负载均衡统一流量的分发与调度。服务网关统一外部请求的协议转换、鉴权、熔断、限流。服务治理熔断、降级、限流、切量、黑白名单等非入侵式增强。框架语言对编程框架的二次封装与扩展。服务组件缓存热 Key 处理、Redis 层路由、活动邀请不唯一短码生成等业务通用组件。数据承载对数据库、缓存、搜索引擎的封装与路由。框架结构脚手架本身统一工程创建标准。部署方式镜像打包、环境编排、自动发布。工具插件IDEA 插件、命令行工具等研发效能工具。一个较成熟的互联网公司大部分场景下的轮子已基本建造完了。剩下的一般是用于解决业务场景下非业务逻辑的通用性组件例如高并发下的缓存热 Key、Redis 层路由、活动邀请的不唯一短码生成等等类似这样的场景。但此类场景的轮子建设也是非常有价值的在公司层面使用稳定后还可以推广到市场获得一定的认可。二、什么是脚手架呢What is scaffolding? Is it a term for a particular platform?Scaffolding is a meta-programming method of building database-backed software applications. It is a technique supported by some model-view-controller frameworks, in which the programmer may write a specification that describes how the application database may be used. The compiler uses this specification to generate code that the application can use to create, read, update and delete database entries, effectively treating the template as a scaffold on which to build a more powerful application.结合社区上的这段定义脚手架是一种元编程方法用于构建基于数据的应用。创建系统架构的程序员编写一份规格说明书用于描述怎么去使用数据库。而脚手架可以根据这份规则说明书生成相应的框架代码。我们把这种模式称为脚手架在脚手架上更高效的构建出 powerful 的应用说白了就是简化具有共性重复操作的简单工作不再需要程序员还得一点点粘贴复制克隆出一个已经存在的架构。只需要在界面或者公用接口上传入必要的参数就可以创建出一个应用开发框架。三、谁提供了脚手架1、Spring 官网脚手架推荐⭐⭐⭐⭐描述Spring Initializr 本质上也是一个 Web 应用它可以通过 Web 界面、Spring Tool Suite、IntelliJ IDEA 等方式构建出一个基本的 Spring Boot 项目结构。同时可以使用它的源码进行本地部署。2、阿里云脚手架推荐⭐⭐⭐⭐描述Aliyun Java Initializr 和 Spring Initializr 是同类的 Web 服务是代码框架生成器一键生成你的代码框架有完善的工具链免费的 IDEA 插件方便直接在 IDE 中生成同时也非常适合国内用户的网络环境。其实这两个脚手架都能很好的生成项目结构让程序员可以在统一的标准下快速的进入开发环境。只是依赖于自身选择的支撑服务选择不同的框架就可以了。四、手撸一个脚手架都有脚手架了那为什么要自己撸一个呢脚手架的目的是为了在统一的标准下快速建设系统框架把系统开发过程中需要的配置、组件、服务、测试一并通过配置引入到系统开发中。但有些时候在互联网公司通用的脚手架是不太合适使用的因为它没有把公司内的一些自研性质的组件引入进去也不能很好的融合。如果已经用脚手架生成后还得需要研发人员自己大量复制进去一些特定的组件就破坏了脚手架本身能力也是破坏了准则和规范。所以需要结合脚手架的开发能力包装各类特定组件、服务、配置实现符合公司领域的统一脚手架。那么接下来就带着大家看看一个脚手架该如何开发实现。其实并没有太复杂我们可以使用 FreeMarker 的能力构建系统框架。1. 工程框架EasyRiggerInitializr └── src ├── main │ ├── java │ │ └── cn.bugstack.initializr.rigger │ │ ├── application │ │ │ └── IProjectGenerator.java │ │ ├── domain │ │ │ ├── model │ │ │ │ └── ApplicationInfo.java │ │ │ │ └── ProjectInfo.java │ │ │ └── service │ │ │ ├── module │ │ │ │ ├── impl │ │ │ │ │ ├── GenerationApplication.java │ │ │ │ │ ├── GenerationIgnore.java │ │ │ │ │ ├── GenerationPackageInfo.java │ │ │ │ │ ├── GenerationPom.java │ │ │ │ │ ├── GenerationTest.java │ │ │ │ │ └── GenerationYml.java │ │ │ │ └── BaseModule.java │ │ │ └── ProjectGeneratorImpl.java │ │ └── RiggerApplication.java │ └── resources │ ├── generator │ │ ├── application.ftl │ │ ├── ignore.ftl │ │ ├── package-info.ftl │ │ ├── pom.ftl │ │ ├── test.ftl │ │ └── yml.ftl │ └── application.yml └── test └── java └── cn.bugstack.initializr.rigger.test └── ApiTest.java整个用于创建脚手架的工程并不复杂主要就是通过 FreeMarker 对各类定义的 ftl 模板文件生成对应的系统框架结构。这里包括工程主体、框架结构、启动类、配置文件、测试类等也可以结合自身需求把对应 ORM 的类和映射关系生成出来。整个工程结构偏 DDD 层次结构domain 领域中建设了所有的生成方式resources/generator定义生成模板其他地方就没有太大的差异了。关于 DDD 分层结构的详细拆解可以参考仓库中的 MVC2DDD - 架构重构 一文其中讲解了 DDD 各分层应用层、领域层、基础层、触发器层等的职责与调用链路。接下来简单介绍下这个工程的代码让大家可以理解这样的工程是如何开发的也可以通过这样工程继续完善成自己需要的结构。2. 应用层定义生成类接口cn.bugstack.initializr.rigger.application.IProjectGenerator.javapublic interface IProjectGenerator { void generator(ProjectInfo projectInfo) throws Exception; }DDD 的分层结构通常都会在 application 这个比较薄的层定义接口再由 domain 领域层做相应的实现。这个接口的定义主要是为了让外部调用方可以通过此接口创建工程框架。3. FTL 模板定义什么是 FreeMarker?FreeMarker 是一款模板引擎即一种基于模板和要改变的数据并用来生成输出文本HTML 网页电子邮件配置文件源代码等的通用工具。它不是面向最终用户的而是一个 Java 类库是一款程序员可以嵌入他们所开发产品的组件。模板编写为 FreeMarker Template LanguageFTL。它是简单的、专用的语言不是像 PHP 那样成熟的编程语言。那就意味着要准备数据在真实编程语言中来显示比如数据库查询和业务运算之后模板显示已经准备好的数据。在模板中你可以专注于如何展现数据而在模板之外可以专注于要展示什么数据。3.1 application.ftl这是生成 SpringBoot 启动类的模板${packageName}与${className}是两个动态占位符在运行时由模板引擎用实际数据替换package ${packageName}; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class ${className} { public static void main(String[] args) { SpringApplication.run(${className}.class, args); } }3.2 pom.ftl这是生成 Maven 工程描述文件 pom.xml 的模板通过${groupId}、${artifactId}、${version}、${name}、${description}五个占位符注入工程元数据并统一继承 SpringBoot 官方父工程保证依赖版本的一致性?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.1.6.RELEASE/version relativePath/ !-- lookup parent from repository -- /parent groupId${groupId}/groupId artifactId${artifactId}/artifactId version${version}/version name${name}/name description${description}/description /project3.3 yml.ftl这是生成 application.yml 配置文件的模板示例中预置了服务端口 8081实际使用时可以按需扩展数据源、Redis、MQ 等配置项server: port: 8081以上只是用于生成框架文件的基础 ftl 文件有需要一些特殊判断和逻辑的可以参考 FreeMarker 在线手册编写自己需要的 ftl 文件。例如可以在模板中使用#if、#list等 FTL 指令做条件判断与循环生成让一个模板适配多种工程形态。4. FTL 生成文件cn.bugstack.initializr.rigger.domain.service.module.impl.GenerationApplication.javaService public class GenerationApplication extends BaseModule { private Logger logger LoggerFactory.getLogger(GenerationApplication.class); public void doGeneration(ProjectInfo projectInfo, String projectsRoot, String lastPackageName, StringBuffer applicationJavaName) throws Exception { ApplicationInfo applicationInfo new ApplicationInfo( projectInfo.getGroupId() . lastPackageName, applicationJavaName.toString() ); String packagePath applicationInfo.getPackageName().replace(., /) /; File file new File(projectsRoot projectInfo.getArtifactId() /src/main/java/ packagePath, applicationInfo.getClassName() .java); // 写入文件 super.writeFile(file, application.ftl, applicationInfo); logger.info(创建主入口类 Application.java {}, file.getPath()); } }关于 ftl 文件的使用无论用于生成哪一层的文件基本都是通用。这里只展示一下关于 Application.java 的创建定义入参ApplicationInfo封装生成启动类所需的包名与类名。定义文件位置把包名中的.替换为/拼装出/src/main/java/下的完整物理路径。写入到文件super.writeFile由父类BaseModule统一封装 FreeMarker 模板与数据模型的合并、以及文件的落盘逻辑。类中的lastPackageName与applicationJavaName是在框架入口ProjectGeneratorImpl中根据artifactId计算得出的下一节会看到完整的计算过程。5. 创建框架入口cn.bugstack.initializr.rigger.domain.service.ProjectGeneratorImpl.javaService public class ProjectGeneratorImpl implements IProjectGenerator { private Logger logger LoggerFactory.getLogger(ProjectGeneratorImpl.class); Resource private GenerationApplication generationApplication; Resource private GenerationYml generationYml; Resource private GenerationPom generationPom; Resource private GenerationTest generationTest; Resource private GenerationIgnore generationIgnore; Resource private GenerationPackageInfo generationPackageInfo; Override public void generator(ProjectInfo projectInfo) throws Exception { URL resource this.getClass().getResource(/); String projectsRoot resource.getFile() /projects/; String lastPackageName projectInfo.getArtifactId().replaceAll(-, ).toLowerCase(); //启动类名称 String[] split projectInfo.getArtifactId().split(-); StringBuffer applicationJavaName new StringBuffer(); Arrays.asList(split).forEach(s - { applicationJavaName.append(s.substring(0, 1).toUpperCase() s.substring(1)); }); applicationJavaName.append(Application); // 1. 创建 Application.java generationApplication.doGeneration(projectInfo, projectsRoot, lastPackageName, applicationJavaName); // 2. 生成 application.yml generationYml.doGeneration(projectInfo, projectsRoot); // 3. 生成 pom.xml generationPom.doGeneration(projectInfo, projectsRoot); // 4. 创建测试类 ApiTest.java generationTest.doGeneration(projectInfo, projectsRoot, lastPackageName, applicationJavaName); // 5. 生成 .gitignore generationIgnore.doGeneration(projectInfo, projectsRoot); // 6. DDD 四层描述文件 generationPackageInfo.doGeneration(projectInfo, projectsRoot, lastPackageName, applicationJavaName); } }ProjectGeneratorImpl类就是应用层接口IProjectGenerator在领域层的具体实现。这里包括了如下内容创建 Application.java生成 application.yml生成 pom.xml创建测试类 ApiTest.java生成 .gitignoreDDD 四层描述文件其中两个关键推导逻辑值得展开说明包名推导lastPackageName projectInfo.getArtifactId().replaceAll(-, ).toLowerCase()例如artifactId web-test则推导出包名后缀webtest最终包名为groupId .webtest即cn.bugstack.demo.webtest。启动类名推导按-拆分artifactId对每一段做首字母大写拼接再追加Application后缀。例如web-test会生成启动类名WebTestApplication。这个规则保证了创建出来的工程可以直接运行也体现了脚手架统一规范的价值。综上就是整个脚手架生成的简要介绍其实并没有多复杂主要就是 ftl 文件的定义和使用这种创建脚手架的方式还是很方便的。6. 测试验证单元测试Test public void test_IProjectGenerator() throws Exception { ProjectInfo projectInfo new ProjectInfo( cn.bugstack.demo, web-test, 1.0.0-SNAPSHOT, web-test, Demo project for Spring Boot ); iProjectGenerator.generator(projectInfo); }脚手架把创建出来的工程生成到test-classes下的projects目录中这个路径在ProjectGeneratorImpl中通过this.getClass().getResource(/)取得 classpath 根路径后拼装也可以配置到其他路径里。有了新生成的工程就可以通过 IDEA 打开了与我们手动创建的工程是一样的。运行单元测试后即可在对应输出目录看到包含pom.xml、application.yml、启动类、测试类、.gitignore的完整工程骨架。五、从模板生成到DDD 脚手架的演进上文演示的 FreeMarker 脚手架方案核心价值在于通过 ftl 模板 入参数据批量生成工程文件。而这一思路在 CodeGuide 仓库后续的脚手架实践中被进一步放大演进出两条更完整的落地路径1. 面向 Maven 的 DDD 脚手架archetype 方案仓库的 DDD 脚手架【Maven 本地版】 与 DDD 脚手架【Maven 在线版】 两篇文档展示了另一套脚手架实现机制使用maven-archetype-plugin的archetype:create-from-project命令把已经搭好的 DDD 工程逆向生成为脚手架模板再通过mvn clean install安装到本地 Maven 仓库之后在 IntelliJ IDEA 创建工程时选择 Maven Archetype 即可一键生成 DDD 四层架构工程。这套 DDD 脚手架分两套形态轻量版lite以提供 HTTP 服务为主缓存使用 Guava、任务使用 Quartz、消息使用 Spring/Guava 替代适合中小业务场景。标准版std综合使用分布式技术栈Dubbo 提供 RPC 接口 Nacos 作为注册中心、Redis 提供缓存与加锁、ShardingJdbc 提供分库分表、XXL-JOB 提供分布式任务调度、RocketMQ 提供异步消息适合中大型场景。值得注意的在线版实现细节是脚手架本质上是一个 Jar 包而 Maven 构建工程时产生的archetype-catalog.xml就是脚手架的定义文件把它上传到可公网访问的域名根目录再在 IDEA 中配置该地址即可让脚手架以在线目录的方式被所有人使用。这与本文手撸 FreeMarker 脚手架殊途同归——目标都是把通用、共性的工程搭建动作沉淀为可复用的产物。2. 面向 IDEA 插件的 DDD 脚手架freemarker 方案仓库中 《IntelliJ IDEA 插件开发》第四节扩展创建工程向导步骤开发DDD脚手架 则把 FreeMarker 与 IDEA 插件能力结合通过继承ModuleBuilder、ModuleWizardStep扩展创建工程向导并使用ProjectTemplatesFactory注册自定义工程模板。其 domain 层核心服务同样基于 FreeMarkerAbstractProjectGenerator extends FreemarkerConfiguration通过模板方法抽象出 DDD 分层结构application、domain、infrastructure、interfaces的创建流程。这恰好印证了本文脚手架方案的可扩展性——同一套FTL 模板 数据模型的思想既可以用于 Web 接口方式创建工程也可以内嵌到 IDE 中成为向导步骤。3. 脚手架在实战项目中的落地脚手架不只是玩具它已经在仓库的实战项目中真实落地。例如 大营销平台系统 - 使用脚手架创建工程 中就明确使用 DDD 脚手架完成工程创建并推送代码到仓库Lottery 抽奖系统 - 搭建DDD四层架构 中也指出一般在互联网企业这部分工作可能不需要反复处理只需要在承接产品需要后使用脚手架或者直接复制以往工程就可以创建现有需要使用的工程了但由于需要基于 DDD 四层架构进行模块化拆分并引入 RPC 等分布式组件才有了自建脚手架的必要。这正是本文开篇所述公司通用脚手架不适配自研技术栈的真实注脚。六、总结站在公司角度不重复造轮子是为了各部门职责和资源的成本但对个人来说不能因为一句不重复造轮子就放弃了对知识栈深入学习的机会。没有这些根基的学习也压根不会理解技术的迁移、服务的提取、组件的凝练。反反复复的总是做一些 API 的应用包壳对个人技术上也就没有什么成长。哪怕公司不需要你造轮子没关系你可以造给自己可以分享到社区。一方面是自己的学习汇总另一方面也是对技术的沉淀和贡献。核心要点回顾本文通过EasyRiggerInitializr工程完整演示了应用层定义接口 → 领域层按模块实现生成逻辑 →resources/generator下定义 FTL 模板 →ProjectGeneratorImpl编排生成流程 → 单元测试验证产出的脚手架开发全链路。这套方案的本质是把 FreeMarker 模板引擎作为元编程工具用模板 数据模型的方式把工程创建的共性操作程序化。后续无论是演进为 Maven Archetype 形式的 DDD 脚手架还是内嵌到 IDEA 插件中核心思想都是一致的以模板化、配置化的方式把企业技术栈沉淀为统一、可复用的工程生成能力。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐SingGuard-4b-GGUF最佳实践构建企业级AI安全防护系统的10个技巧SingGuard 4b GGUF最佳实践构建企业级AI安全防护系统的10个技巧 SingGuard 4b GGUF是一款策略自适应的多模态LLM安全防护模型蛋白质复合物建模实战使用RoseTTAFold预测蛋白质相互作用蛋白质复合物建模实战使用RoseTTAFold预测蛋白质相互作用 蛋白质 蛋白质相互作用PPI是生命活动的核心机制而准确预测蛋白质复合物结构对理解疾病机人工智能深度学习生物信息学科学计算终极指南3分钟掌握Steam游戏数据提取的完整方法终极指南3分钟掌握Steam游戏数据提取的完整方法 想要快速获取Steam游戏信息却不知道从何入手GetDataFromSteam SteamDB这款开源工网页爬虫上一篇10分钟上手openFrameworks三大平台安装与第一个项目创建完整指南下一篇驱动冗余拖慢系统DriverStore Explorer帮你释放20GB空间的完整方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考