Spring Initializr 自定义扩展代码生成器平台
项目代码生成器,构建主要基于Spring 形式的项目. 该项目基于Spring Initializr作为代码生成基础基座,然后基于起提供的扩展能力(Contributor对象),来进行项目相关组成部分的生成,本扩展项目提供基于项目业务代码生成、项目监控数据采集等.
Spring Initializr 介绍
Spring Initializr整体介绍
Spring Initializr提供了一个可扩展的API,以生成基于JVM的项目,并检查用于生成项目的元数据,例如列出可用的依赖项和版本。
模块信息说明
名称 | 说明 |
---|---|
initializr-actuator | 可选模块,提供有关项目生成的其他信息和统计信息。 |
initializr-bom | 提供bom,以简化项目中的依赖项管理 |
initializr-docs | 文档生成管理 |
initializr-generator | 核心项目生成库。 |
initializr-generator-spring | 可选模块,用于定义典型Spring Boot项目的约定。 可以重复使用或由您自己的约定替换 |
initializr-generator-test | 测试项目生成的基础架构 |
initializr-metadata | 项目各个方面的元数据基础架构 |
initializr-service-sample | 展示基本的自定义实例 |
initializr-version-resolver | 可选模块,用于从任意POM中提取版本号。 |
initializr-web | 为第三方提供的web rest接口项目 |
Initializr Generator
initializr-generator模块包含生成基于JVM的项目所必需的低级基础结构。ProjectGenerator
类是项目生成的主要入口点。 ProjectGenerator
采用一个ProjectDescription
来定义要生成的特定项目,以及一个ProjectAssetGenerator
的实现,该实现负责根据可用候选者生成资产。
项目生成发生在专用的应用程序上下文(ProjectGenerationContext)中,这意味着对于所生成的每个项目,上下文仅包含与该特定项目相关的配置和组件。 在@ProjectGenerationConfiguration注释的配置类中定义了ProjectGenerationContext的候选组件。 如果这些配置类已在META-INF/spring.factories中注册,则会自动导入,如以下示例所示:
io.spring.initializr.generator.project.ProjectGenerationConfiguration=\
com.example.acme.build.BuildProjectGenerationConfiguration,\
com.example.acme.code.SourceCodeProjectGenerationConfiguration
通常使用条件使添加到ProjectGenerationContext的组件可用。 使用条件可以避免暴露那些必须检查是否必须执行某些操作的bean,并使声明更加惯用。 考虑以下示例:
@Bean
@ConditionalOnBuildSystem(GradleBuildSystem.ID)
@ConditionalOnPackaging(WarPackaging.ID)
public BuildCustomizer<GradleBuild> warPluginContributor() {
return (build) -> build.plugins().add("war");
}
这将注册仅在要生成的项目使用“ Gradle” BuildSystem和“ war” Package时才能自定义Gradle构建的组件。 检查io.spring.initializr.generator.condition包以了解更多条件。 您可以通过从ProjectGenerationCondition继承轻松创建自定义条件。您只能在已加载到ProjectGenerationConfiguration上的bean上使用这些条件,因为它们需要具体的ProjectDescription bean才能正常运行。
项目生成还可能依赖于特定于特定项目配置的基础结构,该基础结构通常在主ApplicationContext中进行配置,以避免每次出现新请求时都将其注册。一个常见的用例是将主ApplicationContext设置为以下项的父项: ProjectGenerationContext,如以下示例所示:
public ProjectGenerator createProjectGenerator(ApplicationContext appContext) {
return new ProjectGenerator((context) -> {
context.setParent(appContext);
context.registerBean(SampleContributor.class, SampleContributor::new);
});
}
这将创建一个新的ProjectGenerator,它可以使用应用程序的任何bean,注册在META-INF / spring.factories中找到的所有贡献者,还可以通过编程方式注册其他ProjectContributor。
ProjectContributor是一个最高级别的界面,可以用来为项目贡献资产。 上面注册的SampleContributor在项目结构的根目录下生成一个test.txt文件,如下所示:
/***
* 代码生成自定义贡献者
*/
public class SampleContributor implements ProjectContributor {
@Override
public void contribute(Path projectRoot) throws IOException {
//生成hello.txt项目文件
Path file = Files.createFile(projectRoot.resolve("hello.txt"));
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(file))) {
writer.println("Test");
}
}
}
当指示ProjectGenerator生成项目时,可以使用可用的ProjectDescriptionCustomizer Bean自定义指定的ProjectDescription,也可以使用Spring的Ordered接口对其进行执行顺序调整。 对于希望知道原始ProjectDescription的属性是否已修改的扩展,可以使用ProjectDescriptionDiff Bean。
一旦根据可用的ProjectDescriptionCustomizers对描述进行了自定义,生成器将使用ProjectAssetGenerator生成项目资产。 initializr-generator模块提供了此接口的默认实现(DefaultProjectAssetGenerator),该接口使用可用的ProjectContributor Bean生成目录结构。
虽然默认的ProjectAssetGenerator使用文件系统并调用一组特定的组件,但可以将同一ProjectGenerator实例与自定义实现一起使用,而该实现完全侧重于其他事项。
项目生成执行流程
-
第三方调用REST接口initializr-web/ProjectGenerationController
private final ProjectGenerationInvoker<R> projectGenerationInvoker; public ProjectGenerationController(InitializrMetadataProvider metadataProvider, ProjectGenerationInvoker<R> projectGenerationInvoker) { this.metadataProvider = metadataProvider; this.projectGenerationInvoker = projectGenerationInvoker; } @RequestMapping("/starter.zip") public ResponseEntity<byte[]> springZip(R request) throws IOException { //通过projectGenerationInvoker项目生成执行器进行结果生成 ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); Path archive = createArchive(result, "zip", ZipArchiveOutputStream::new, ZipArchiveEntry::new, ZipArchiveEntry::setUnixMode); return upload(archive, result.getRootDirectory(), generateFileName(request, "zip"), "application/zip"); }
-
在ProjectGenerationInvoker对象中invokeProjectStructureGeneration方法中获取相关信息(项目元数据信息、生成项目的描述文件信息),然后实例化ProjectGenerator对象进行项目生成
private final ApplicationContext parentApplicationContext; private final ApplicationEventPublisher eventPublisher; private final ProjectRequestToDescriptionConverter<R> requestConverter; /**** 项目结构生成 */ public ProjectGenerationResult invokeProjectStructureGeneration(R request) { //项目元数据获取 InitializrMetadata metadata = this.parentApplicationContext.getBean(InitializrMetadataProvider.class).get(); try { //转换生成项目的描述信息 ProjectDescription description = this.requestConverter.convert(request, metadata); //实例化项目生成对象 ProjectGenerator projectGenerator = new ProjectGenerator(( projectGenerationContext) -> customizeProjectGenerationContext(projectGenerationContext, metadata)); //执行项目生成方法 ProjectGenerationResult result = projectGenerator.generate(description, generateProject(request)); addTempFile(result.getRootDirectory(), result.getRootDirectory()); return result; } catch (ProjectGenerationException ex) { publishProjectFailedEvent(request, metadata, ex); throw ex; } } //项目生成Spring Context 设置 private void customizeProjectGenerationContext(AnnotationConfigApplicationContext context, InitializrMetadata metadata) { context.setParent(this.parentApplicationContext); context.registerBean(InitializrMetadata.class, () -> metadata); context.registerBean(BuildItemResolver.class, () -> new MetadataBuildItemResolver(metadata, context.getBean(ProjectDescription.class).getPlatformVersion())); context.registerBean(MetadataProjectDescriptionCustomizer.class, () -> new MetadataProjectDescriptionCustomizer(metadata)); } //配置项目资源生成的对象:DefaultProjectAssetGenerator private ProjectAssetGenerator<ProjectGenerationResult> generateProject(R request) { return (context) -> { Path projectDir = new DefaultProjectAssetGenerator().generate(context); publishProjectGeneratedEvent(request, context); return new ProjectGenerationResult(context.getBean(ProjectDescription.class), projectDir); }; }
-
ProjectGenerator实例调用
/*** 代码生成 */ public <T> T generate(ProjectDescription description, ProjectAssetGenerator<T> projectAssetGenerator) throws ProjectGenerationException { //获取代码生成上下文 try (ProjectGenerationContext context = this.contextFactory.get()) { //注册ProjectDescription context.registerBean(ProjectDescription.class, resolve(description, context)); //加载配置文件 context.register(CoreConfiguration.class); this.contextConsumer.accept(context); //刷新上线文 context.refresh(); try { //调用项目生成资源文件进行代码生成 return projectAssetGenerator.generate(context); } catch (IOException ex) { throw new ProjectGenerationException("Failed to generate project", ex); } } } //项目生成Configuration配置 @Configuration @Import(ProjectGenerationImportSelector.class) static class CoreConfiguration { } /** * 通过加载 META-INF/spring.factories 键为: io.spring.initializr.generator.project.ProjectGenerationConfiguration 进行相关配置的加载 * {@link ImportSelector} for loading classes configured in {@code spring.factories} * using the * {@code io.spring.initializr.generator.project.ProjectGenerationConfiguration} key. */ static class ProjectGenerationImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { //spring.factories 中加载ProjectGenerationConfiguration配置 List<String> factories = SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class, getClass().getClassLoader()); return factories.toArray(new String[0]); } }
-
ProjectAssetGenerator(默认类:DefaultProjectAssetGenerator),获取项目描述文件、ProjectContributor等获取,并进行项目相关代码生成
// @Override public Path generate(ProjectGenerationContext context) throws IOException { //获取项目描述信息 ProjectDescription description = context.getBean(ProjectDescription.class); //解析文件生成的路径 Path projectRoot = resolveProjectDirectoryFactory(context).createProjectDirectory(description); Path projectDirectory = initializerProjectDirectory(projectRoot, description); //获取项目贡献者-ProjectContributor:项目文件生成实际实现者 List<ProjectContributor> contributors = context.getBeanProvider(ProjectContributor.class).orderedStream() .collect(Collectors.toList()); for (ProjectContributor contributor : contributors) { //进行项目文件生成 contributor.contribute(projectDirectory); } return projectRoot; }
-
ProjectContributor 项目具体资源生成逻辑处理---->代码生成具体实现 ,如gitIgnore 忽略文件生成
//生成.gitignore Git 配置文件 public class GitIgnoreContributor implements ProjectContributor { //gitIgnore 配置相关 private final GitIgnore gitIgnore; public GitIgnoreContributor(GitIgnore gitIgnore) { this.gitIgnore = gitIgnore; } //代码生成实际执行 @Override public void contribute(Path projectRoot) throws IOException { if (this.gitIgnore.isEmpty()) { return; } Path file = Files.createFile(projectRoot.resolve(".gitignore")); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(file))) { this.gitIgnore.write(writer); } } }
自定义Contributor开发
参考:
initializr-generator-spring/io.spring.initializr.generator.spring.doc
案例:
自定义README.md 项目描述文件生成,生成标准的项目结构说明,标准化项目结构
自定义开发步骤:
- 在initializr-generator-spring项目中创建package包,定义ReadMeContributor + ReadMeConfiguration配置类
- META-INF/spring.factories中io.spring.initializr.generator.project.ProjectGenerationConfiguration 配置ReadMeConfiguration 进行ReadMeContributor注入
- 基于ReadMeContributor 业务场景进行相关对象的声明定义
Spring Initializr 扩展
业务代码 Contributor
参考:
BCC(Business Code Contributor):业务代码生成,主要提供基础的业务代码生成配置,业务代码生成基于Mybatis-Plus代码生成器进行代码生成构建