一、背景:为什么要做脚手架?
创建工程的痛点
2020 年,我们公司迎来了业务发展的迅猛期,滋生大量创建工程的需求。总体来说,创建工程面临着以下几个问题。
- 在创建工程时,多采用 copy 历史工程,并在上面进行修改的方式,造成了新工程里遗留了一些老旧的“垃圾”;
- 各团队所建工程分层方式不一,结构混乱,甚至有的包职责相同,命名却不一样,难以形成共识传递下去;
- 所依赖组件版本不一,比如
jackson
、guava
包,难以形成技术演进,或者说技术演进兼容性问题很难解决;
业内方案参考
start.spring.io
整合了Gradle
, Maven
工程,语言支持Java
,Kotlin
,Groovy
。

start.aliyun.com
在start.spring.io
基础上增加了不同应用架构的选择:MVC
, 分层架构, COLA
。同时也增加阿里的开源组件例如Spring Cloud Alibaba
。

同时也增加了【一键运行】功能,【分享】功能可以保存分享至自己的账号下。
二、构思:做成什么样?
脚手架画像
- 能快速创建一个最小可运行工程;
- 能规范工程命名、服务应用架构分层, 增加代码结构规范、可理解性;
- 能快速集成 CI/CD,代码驱动 API 接口文档生成,提升开发效率;
- 能统一第三方组件版本号;
1.0 版本
为了快速落地脚手架,我们使用了 Maven Archetype 来实现。首先创建一个规范化的工程。
工程结构需分层清晰,像斑马的条纹,因此取名为zebra
。工程已开源:https://github.com/studeyang/zebra

然后使用 Maven 的maven-archetype-plugin
插件,生成脚手架。
1 2 3 4 5 6 7 8 9 10
| <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-archetype-plugin</artifactId> <version>3.0.0</version> <configuration> <propertyFile>archetype.properties</propertyFile> <encoding>UTF-8</encoding> <archetypeFilteredExtentions>java,xml,yml,sh,groovy,md</archetypeFilteredExtentions> </configuration> </plugin>
|
生成的脚手架如下:

脚手架中生成的代码不是可编译的代码,它包含了一些变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package};
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients;
/** * @author studeyang */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class WebApplication {
public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); }
}
|
最后,就可以将脚手架打包并上传至仓库。
1 2 3 4 5 6 7 8 9
| # 1. 修改项目代码,在项目目录执行 cd ${projectRoot} mvn archetype:create-from-project # 2. 然后在 target/generated-sources/archetype 目录下执行下一步的操作 cd target/generated-sources/archetype # 3. 脚手架打包 mvn clean install # 4. 上传脚手架 mvn clean deploy
|
用户可以通过以下命令下载脚手架包。
1 2 3 4 5
| mvn dependency:get \ -DremoteRepositories={仓库地址} \ -DgroupId={脚手架的groupId} \ -DartifactId={脚手架的artifactId} \ -Dversion={版本}
|
下载完成后,使用脚手架生成新工程。
1 2 3 4 5 6 7 8 9
| mvn archetype:generate \ -DarchetypeGroupId={脚手架的groupId} \ -DarchetypeArtifactId={脚手架的artifactId} \ -DarchetypeVersion={脚手架版本} \ -DgroupId={工程的groupId} \ -DartifactId={工程的artifactId} \ -Dversion={工程版本号} \ -Dpackage={工程包名} \ -DinteractiveMode=false
|
2.0 版本
脚手架 1.0 版本解决了上述的大多痛点,但也有一些无法实现的或者可以做得更好的。例如:基础组件依赖无法管理,用户无法灵活的选择工程所需要的依赖等。参考start.spring.io
,我们发现可以做的还有很多,于是启动 2.0 版本的开发。
最终形态:

三、实现:怎么做的?
相比于start.spring.io
,主要变化是增加了分层应用架构,整合了公司自己的组件库,并且新开发了【一键运行】功能。
主要实现
当前端组织好参数后,最终通过 HTTP 请求将参数传递给后端,后端接收到的参数如下。

后端接收到工程类型为maven-project
,并通过如下配置将之识别为zebra-project
,即分层架构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| initializr: types: - name: None id: based-project description: Generate a Maven based project archive. tags: build: maven format: based-project action: /starter.zip - name: 分层架构 id: maven-project description: Generate a Zebra project archive. tags: build: maven format: zebra-project default: true action: /starter.zip
|
我们自定义zebra-project
分层架构的代码实现。这里定义一些 BuildCustomizer,实现工程的一些定制,例如:用户选择了 spring-boot-starter,程序应该在pom.xml
生成相应的 dependency。代码如下:
(Spring Initializr 提供了 BuildCustomizer 接口的扩展性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @ProjectGenerationConfiguration @ConditionalOnProjectFormat(ZebraProjectFormat.ID) public class ZebraProjectGenerationConfiguration { @Bean public BuildCustomizer<Build> springBootStarterBuildCustomizer() { return (build) -> { build.dependencies().add(SPRINGBOOT_STARTER_ID, Dependency.withCoordinates("org.springframework.boot", "spring-boot-starter")); build.dependencies().add("starter-test", Dependency.withCoordinates("org.springframework.boot", "spring-boot-starter-test") .scope(DependencyScope.TEST_COMPILE)); }; } }
|
再定义一些 Contributor,实现工程各个部分结构的生成,例如根目录pom.xml
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @ProjectGenerationConfiguration @ConditionalOnProjectFormat(ZebraProjectFormat.ID) public class ProjectContributorAutoConfiguration { @Bean public ZebraRootPomProjectContributor zebraRootPomProjectContributor( MavenBuild build, IndentingWriterFactory indentingWriterFactory) { return new ZebraRootPomProjectContributor(build, indentingWriterFactory); }
@Bean public ApplicationYmlProjectContributor bootstrapYmlProjectContributor(ProjectDescription description) { return new ApplicationYmlProjectContributor(description); } }
|
”项目添加了什么依赖,工程就生成对应的代码“,对于这个功能点,是通过@ConditionalOnRequestedDependency
注解来实现的。
1 2 3 4 5
| @Bean @ConditionalOnRequestedDependency("web") public OrderServiceImplCodeProjectContributor orderServiceImplCodeProjectContributor() { return new OrderServiceImplCodeProjectContributor(this.description); }
|
例如OrderServiceImplCodeProjectContributor
代码类的生成,只当用户选择了 web 依赖才会生成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class OrderServiceImplCodeProjectContributor implements ProjectContributor { @Override public void contribute(Path projectRoot) throws IOException { JavaCompilationUnit javaCompilationUnit = javaSourceCode.createCompilationUnit( this.description.getPackageName() + ".restful", "OrderServiceImpl"); JavaTypeDeclaration javaTypeDeclaration = javaCompilationUnit.createTypeDeclaration("OrderServiceImpl"); customize(javaTypeDeclaration);
Path servicePath = ContributorSupport.getServicePath(projectRoot, description.getArtifactId()); this.javaSourceCodeWriter.writeTo( new SourceStructure(servicePath.resolve("src/main/"), new JavaLanguage()), javaSourceCode); } }
|
依赖包管理
增加如下配置即可实现新组件库的增加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| initializr: dependencies: - name: 基础组件库 bom: infra repository: my-rep content: - name: Example id: example groupId: com.dbses.open artifactId: example-spring-boot-starter description: 示例组件说明 starter: true links: - rel: guide href: {用户手册} description: Example 快速开始 - rel: reference href: {参考文档}
|
一键运行
一键运行功能是把生成好的工程上传至公司的代码仓库(Gitlab),并做好新工程的 CICD 配置(Jenkins),然后将工程部署到云容器(Kubernetes)的过程。

前端使用的是 React 组件是抖音的 Semi。Gitlab Groups 下拉列表是通过 Gitlab API 授权获取的,这个授权过程如下:

授权完成后,点击确认的后续过程:

createGitlabProjectProcessor
业务处理:
- 完成
gitlab
工程的创建;
- 生成新工程,并上传至
gitlab
;
createDevopsProcessor
业务处理:生成并上传工程服务部署模板;
cicdTriggerProcessor
业务处理:触发PRECI
操作(后续操作由jenkins
回调衔接);
到这里,大致的实现就讲完了。如果你也想搭建一个工程脚手架,欢迎和我交流。