Spring Initializr 自定义扩展代码生成器平台

2,662 阅读6分钟

Spring Initializr 自定义扩展代码生成器平台

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实例与自定义实现一起使用,而该实现完全侧重于其他事项。

项目生成执行流程
  1. 第三方调用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");
    }
    
  2. 在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);
    		};
    }
    
  3. 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]);
    	}
    
    }
    
  4. 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;
    }
    
  5. 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 项目描述文件生成,生成标准的项目结构说明,标准化项目结构

自定义开发步骤:

  1. 在initializr-generator-spring项目中创建package包,定义ReadMeContributor + ReadMeConfiguration配置类
  2. META-INF/spring.factories中io.spring.initializr.generator.project.ProjectGenerationConfiguration 配置ReadMeConfiguration 进行ReadMeContributor注入
  3. 基于ReadMeContributor 业务场景进行相关对象的声明定义

Spring Initializr 扩展

业务代码 Contributor

参考:

Mybatis-Plus

BCC(Business Code Contributor):业务代码生成,主要提供基础的业务代码生成配置,业务代码生成基于Mybatis-Plus代码生成器进行代码生成构建