前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >项目脚手架-spring-initializr

项目脚手架-spring-initializr

原创
作者头像
eeaters
修改2022-03-16 07:55:36
7000
修改2022-03-16 07:55:36
举报
文章被收录于专栏:阿杰阿杰

背景

公司在扩张阶段没有制定统一的开发规范, 不同小组之间不存在技术交流,导致了一些问题:

  1. 相同的依赖存在不同的版本
  2. 相同的功能使用不同的依赖jar实现

有一次spring-boot的版本升级耗时了半年, 并且相同的坑总是会在不同的小组内重复踩坑; 在一次公司大的调整中推出了脚手架的功能,通过spring-boot的自动装配机制将公司的依赖和默认配置再封一层; 基于公司的情况推出了eden(只是一个名字)框架,将常用依赖分组,通过自动装配机制做成start的形式进行依赖,然后到了现在做的事情: 做一个脚手架页面, 实现一个如spring和ali一样的脚手架功能

  1. spring的脚手架: https://start.spring.io/
  2. ali的脚手架

效果

代码地址

实现的效果

  1. 模块化的项目,每个模块初始化时自动生成不同的包结构
  2. 父模块以eden为父依赖,管理各个子模块
  3. 启动类放在指定的子模块下

效果图

项目基本配置
项目基本配置
元信息
元信息
父pom
父pom
启动类
启动类

依赖jar

代码语言:html
复制
<dependency>
    <groupId>io.spring.initializr</groupId>
    <artifactId>initializr-generator-spring</artifactId>
</dependency>
<dependency>
    <groupId>io.spring.initializr</groupId>
    <artifactId>initializr-web</artifactId>
</dependency>

核心接口

ProjectMetadataController

项目元信息接口, 效果图1和图2中, 构建一个项目的一些配置和依赖都是以该controller为入口;

比如: #serviceCapabilitiesV21 是idea创建项目时请求的方法

ProjectGenerationController

项目生成的controller , 终究是要生成一份的目录,根据不同的请求会打包成对应的格式

比如: #springZip 生成一份zip;如果是idea会自动解压一下

InitializrMetadata

初始元信息,控制了很多行为, 通过InitializrProperties将配置化的数据进行绑定,我们的配置化信息都是存储在这里面

ProjectGenerator

项目自动生成器,内置了一个上下文,也就是说每一次请求都会创建独属于该请求的应用上下文, 将请求的信息 ProjectDescription 和项目贡献组件(@ProjectGenerationConfiguration标注的类) 加入到上下文中,进行项目的构建

这个上下文springcloud的bootstrap context一样, 临时干个活,干完就销毁;

ProjectContributor

项目贡献者 , 给一个path路径,不同的Contributor按照各自的功能往该路径下进行贡献; 可以说自定义脚手架就是项目贡献者的增增减减

个性化调整

有一些调整是采用简单粗暴的方式,优先实现当前的需求为主,不过有了

EnvironmentExtensionPostProcessor

官方代码的application.yml文件配置太多,导致我刚开始没细看.后来又绕回来看这个文件的配置

那么我就直接把这个文件拆了一下

  1. application.yml 只配置一些项目基本配置
  2. build-project.yml 只配置一下项目构建的一些配置, 对应图1
  3. build-dependency.yml 配置项目的依赖, 对应图2

通过 EnvironmentExtensionPostProcessor 来保证项目能够正常读取这些配置

ExtensionBeanFactoryPostProcessor

有一些ProjectContributor不想使用,因为上下文是内置的,因此我们在refresh阶段对Bean进行调整, 将不需要使用的Bean排除掉,

代码语言:java
复制
package io.eeaters.initializr.extension;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

public class ExtensionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //不生成 mvnw 和 mvnw.cmd 文件;
        beanDefinitionRegistry.removeBeanDefinition("mavenWrapperContributor");

        //会默认给顶层pom添加一个spring-boot-dependencies ; 构建时直接将eden写进去
        beanDefinitionRegistry.removeBeanDefinition("initializrMetadataMavenBuildCustomizer");

        //顶层pom不引入spring-boot-starter
        beanDefinitionRegistry.removeBeanDefinition("defaultStarterContributor");

        //顶层pom不支持module标签, 使用ModuleMavenBuild替换
        beanDefinitionRegistry.removeBeanDefinition("mavenBuild");
        //因原生的没有写入module标签功能, 使用CustomMavenBuildProjectContributor替换
        beanDefinitionRegistry.removeBeanDefinition("mavenBuildProjectContributor");

        //顶层不需要main方法,main入口放在application模块中
        beanDefinitionRegistry.removeBeanDefinition("mainJavaSourceCodeProjectContributor");
        //顶层不需要test模块
        beanDefinitionRegistry.removeBeanDefinition("testJavaSourceCodeProjectContributor");
        //顶层不需要application.properties配置文件, 放在application模块中
        beanDefinitionRegistry.removeBeanDefinition("applicationPropertiesContributor");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

ReadMeGenerationConfiguration

添加README.md可能是最简单的自定义配置了; 参考了一下mvnw和mvnw.cmd的添加,将已经存在的文件复制一份到ROOT文件目录下(即:Project下面), 代码示例:

代码语言:java
复制
@ProjectGenerationConfiguration
public class ReadMeGenerationConfiguration {

    @Bean
    public ProjectContributor readmeProjectContributor(){
        return new MultipleResourcesProjectContributor("contributor/readme");
    }
}

ModuleGenerationConfiguration

模块化的结构,是我本次做的最大的调整, 在ExtensionBeanFactoryPostProcessor中我已经将一部分Contributor给排除了; 然后这里再新增一个模块化的Contributor

代码语言:java
复制
@ProjectGenerationConfiguration
public class ModuleGenerationConfiguration {

    private final ProjectDescription projectDescription;
    private final IndentingWriterFactory indentingWriterFactory;
    private final InitializrMetadata initializrMetadata;

    public ModuleGenerationConfiguration(ProjectDescription projectDescription, IndentingWriterFactory indentingWriterFactory, InitializrMetadata initializrMetadata) {
        this.projectDescription = projectDescription;
        this.indentingWriterFactory = indentingWriterFactory;
        this.initializrMetadata = initializrMetadata;
    }

    /**
     * 模块化的贡献者
     * @return
     */
    @Bean
    public ProjectContributor mouduleContributor() {
        return new ModuleContributor(projectDescription, indentingWriterFactory,initializrMetadata);
    }

    /**
     * 项目父结构下的pom.xml文件生成器 <br/>
     * {@link MavenBuild} 没有module标签功能; 并且parent写死为spring-boot-dependencies
     * @param build
     * @return
     */
    @Bean
    public CustomMavenBuildProjectContributor customMavenBuildProjectContributor(ModuleMavenBuild build) {
        return new CustomMavenBuildProjectContributor(build, indentingWriterFactory);
    }

}

CustomMavenBuildProjectContributor

父pom需要支持eden-parent为父依赖, 并且需要支持moudule模块,项目的依赖由子模块引入, 基本将MavenBuildWriter

代码语言:java
复制
public class CustomMavenBuildProjectContributor implements BuildWriter, ProjectContributor {

    private final ModuleMavenBuild build;

    private final IndentingWriterFactory indentingWriterFactory;

    private final CustomMavenBuildWriter buildWriter = new CustomMavenBuildWriter();


    public CustomMavenBuildProjectContributor(ModuleMavenBuild build, IndentingWriterFactory indentingWriterFactory) {
        this.build = build;
        this.indentingWriterFactory = indentingWriterFactory;
    }


    @Override
    public void writeBuild(Writer out) throws IOException {
        try (IndentingWriter writer = this.indentingWriterFactory.createIndentingWriter("maven", out)) {
            this.buildWriter.writeTo(writer, this.build);
        }
    }

    @Override
    public void contribute(Path projectRoot) throws IOException {
        Path pomFile = Files.createFile(projectRoot.resolve("pom.xml"));
        writeBuild(Files.newBufferedWriter(pomFile));
    }
}

对于ModuleMavenBuild就是增加以下module标签

代码语言:java
复制
public class ModuleMavenBuild extends MavenBuild {

    private List<ModuleContainer> moduleContainer;

    public List<ModuleContainer> getModuleContainer() {
        return moduleContainer;
    }
    public ModuleMavenBuild setModuleContainer(List<ModuleContainer> moduleContainer) {
        this.moduleContainer = moduleContainer;
        return this;
    }

    public static class ModuleContainer{

        private String moduleName;

        public ModuleContainer(String moduleName) {
            this.moduleName = moduleName;
        }

        public String getModuleName() {
            return moduleName;
        }

        public ModuleContainer setModuleName(String moduleName) {
            this.moduleName = moduleName;
            return this;
        }
    }

}

然后增加module的打印功能

代码语言:java
复制
public class CustomMavenBuildWriter {
    public void writeTo(IndentingWriter writer, ModuleMavenBuild build) {
        MavenBuildSettings settings = build.getSettings();
        writeProject(writer, () -> {
            writeParent(writer, build);
            writeProjectCoordinates(writer, settings);
            writePackaging(writer, settings);
            writeProjectName(writer, settings);
            writeProperties(writer, build.properties());
            writeModule(writer, build.getModuleContainer());
            writeDependencyManagement(writer, build.boms());
        });
    }

    private void writeModule(IndentingWriter writer, List<ModuleMavenBuild.ModuleContainer> moduleContainer) {
        writeCollectionElement(writer, "modules", moduleContainer, (writ, module) -> {
            writeSingleElement(writ, "module", module.getModuleName());
        });
    }
    ...基本引用了MavenBuildWriter的代码
}

对于怎么样指定一些配置; initializr有一个Custom来提供这方面的功能

代码语言:java
复制
public class ParentMavenBuildCustomizer implements BuildCustomizer<MavenBuild> {

    private final ProjectDescription description;

    private final InitializrMetadata metadata;

    public ParentMavenBuildCustomizer(ProjectDescription description, InitializrMetadata metadata) {
        this.description = description;
        this.metadata = metadata;
    }
    @Override
    public void customize(MavenBuild build) {
        build.settings().name(this.description.getName())
                .description("generate by eden-initializer")
                .packaging("pom");
        build.properties()
                .property("java.version", "1.8")
                .property("project.build.sourceEncoding", "UTF-8")
                .property("project.reporting.outputEncoding", "UTF-8");

        build.plugins()
                .add("org.springframework.boot", "spring-boot-maven-plugin");

        Version platformVersion = description.getPlatformVersion();
        build.settings().parent("com.freemud.eden", "eden-parent", edenVersion(platformVersion));

        for (ModuleEnum value : ModuleEnum.values()) {

            BillOfMaterials materials = BillOfMaterials.withCoordinates(description.getGroupId(),
                            description.getArtifactId() + value.getModuleNameSuffix())
                    .version(VersionReference.ofProperty("project.version"))
                    .build();
            build.boms().add(value.getModuleNameSuffix(), materials);
        }
    }


    private String edenVersion(Version version) {
        StringBuilder sb = new StringBuilder().append(version.getMajor())
                .append(".")
                .append(version.getMinor())
                .append(".")
                .append(version.getPatch());
        return sb.toString();
    }
}

ModuleContributor

代码语言:java
复制
public class ModuleContributor implements ProjectContributor {

    private final ProjectDescription projectDescription;
    private final IndentingWriterFactory indentingWriterFactory;
    private final InitializrMetadata initializrMetadata;
    public ModuleContributor(ProjectDescription projectDescription, IndentingWriterFactory indentingWriterFactory, InitializrMetadata initializrMetadata) {
        this.projectDescription = projectDescription;
        this.indentingWriterFactory = indentingWriterFactory;
        this.initializrMetadata = initializrMetadata;
    }


    @Override
    public void contribute(Path projectRoot) throws IOException {
        String artifactIdName = projectDescription.getArtifactId();
        for (ModuleEnum value : ModuleEnum.values()) {
            String moduleName = value.moduleName(artifactIdName);
            try {
                Constructor<? extends ModuleStruct> constructor = value.getModuleStructClass()
                        .getConstructor(ProjectDescription.class, String.class, IndentingWriterFactory.class, InitializrMetadata.class);
                ModuleStruct moduleStruct = constructor.newInstance(projectDescription, moduleName, indentingWriterFactory, initializrMetadata);
                moduleStruct.build(projectRoot.resolve(moduleName));
            } catch (Exception e) {
                //暂不处理
                throw new IOException(e);
            }
        }
    }
}

ModuleStruct

不同的module都具有不同的特性; 先拿三个模块试试手

  1. common模块: 将依赖的jar引入
  2. manager模块: 引入common模块,特定的包结构
  3. application模块: 引入manager模块,包含一个EdenApplication的启动类
代码语言:java
复制
public abstract class ModuleStruct {

    //构建maven模块
    private final ProjectDescription description;

    private final String moduleName;

    private final IndentingWriterFactory indentingWriterFactory;

    private final InitializrMetadata initializrMetadata;

    public ModuleStruct(ProjectDescription projectDescription,
                        String moduleName,
                        IndentingWriterFactory indentingWriterFactory,
                        InitializrMetadata initializrMetadata) {
        this.description = projectDescription;
        this.moduleName = moduleName;
        this.indentingWriterFactory = indentingWriterFactory;
        this.initializrMetadata = initializrMetadata;
    }

    public void build(Path moduleRoot) throws IOException {
        writeModule(moduleRoot);
        writePom(moduleRoot);
    }


    protected void writePom(Path moduleRoot) throws IOException {
        MavenBuild mavenBuild = new MavenBuild();
        ProjectDescription projectDescription = getProjectDescription();
        String moduleName = getModuleName();
        mavenBuild.settings().parent(projectDescription.getGroupId(), projectDescription.getArtifactId(), projectDescription.getVersion());
        mavenBuild.settings().artifact(moduleName);
        //每个模块添加不同的依赖
        custom().accept(mavenBuild);

        Path pomFile = Files.createFile(moduleRoot.resolve("pom.xml"));
        IndentingWriter writer = indentingWriterFactory.createIndentingWriter("maven", Files.newBufferedWriter(pomFile));

        MavenBuildWriter mavenBuildWriter = new MavenBuildWriter();
        mavenBuildWriter.writeTo(writer, mavenBuild);
        writer.flush();
    }

    protected void writeModule(Path moduleRoot) throws IOException {
        ProjectDescription description = getProjectDescription();
        SourceStructure sourceStructure = description.getBuildSystem().getMainSource(moduleRoot, description.getLanguage());

        Path resourcesDirectory = sourceStructure.getResourcesDirectory();
        Files.createDirectories(resourcesDirectory);

        Path packagePath = sourceStructure.getSourcesDirectory().resolve(description.getPackageName().replace('.', '/'));
        buildPackage(moduleRoot, packagePath);
    }

    protected void buildPackage(Path moduleRoot, Path packageRootPath) throws IOException {

    }

    protected Consumer<MavenBuild> custom() {
        return build -> {
        };
    }

    protected ProjectDescription getProjectDescription() {
        return description;
    }

    protected String getModuleName() {
        return moduleName;
    }

    protected IndentingWriterFactory getIndentingWriterFactory() {
        return indentingWriterFactory;
    }

    public InitializrMetadata getInitializrMetadata() {
        return initializrMetadata;
    }
}

common模块核心将依赖添加

代码语言:java
复制
public class CommonModuleStruct extends ModuleStruct {

    public CommonModuleStruct(ProjectDescription projectDescription,
                              String moduleName,
                              IndentingWriterFactory indentingWriterFactory,
                              InitializrMetadata initializrMetadata) {
        super(projectDescription, moduleName, indentingWriterFactory, initializrMetadata);
    }

    @Override
    public void buildPackage(Path moduleRoot, Path packageRootPath) throws IOException {
        Files.createDirectories(packageRootPath.resolve("config"));
        Files.createDirectories(packageRootPath.resolve("constant"));
        Files.createDirectories(packageRootPath.resolve("util"));
    }

    /**
     * 搞一下eden的依赖; 或者sdk即可; 不搞那些 版本管理的 dependencies
     * @return
     */
    @Override
    protected Consumer<MavenBuild> custom() {
        ProjectDescription description = getProjectDescription();
        //先省事不到元信息中取了
        InitializrMetadata metadata = getInitializrMetadata();
        return mavenBuild -> {
            description.getRequestedDependencies().forEach((group,dependency)->{
                mavenBuild.dependencies().add(group, dependency);
            });
        };
    }
}

manager核心具有特定的包结构,并引入common依赖

代码语言:java
复制
public class ManagerModuleStruct extends ModuleStruct{

    public ManagerModuleStruct(ProjectDescription projectDescription,
                               String moduleName,
                               IndentingWriterFactory indentingWriterFactory,
                               InitializrMetadata initializrMetadata) {
        super(projectDescription, moduleName, indentingWriterFactory, initializrMetadata);
    }

    @Override
    public void buildPackage(Path moduleRoot, Path packageRootPath) throws IOException {
        Files.createDirectories(packageRootPath.resolve("adapter"));
        Files.createDirectories(packageRootPath.resolve("atom"));
        Files.createDirectories(packageRootPath.resolve("client"));
        Files.createDirectories(packageRootPath.resolve("enums"));
        Files.createDirectories(packageRootPath.resolve("handler"));
        Files.createDirectories(packageRootPath.resolve("helper"));
    }

    @Override
    public Consumer<MavenBuild> custom() {
        ProjectDescription description = getProjectDescription();
        return mavenBuild -> {
            mavenBuild.dependencies()
                    .add("common", Dependency.withCoordinates(description.getGroupId(), description.getArtifactId() + "-common").build());
        };
    }
}

application模块核心是引入manager;并拥有一个启动类

代码语言:java
复制
public class ApplicationModuleStruct extends ModuleStruct{

    private SourceCodeWriter sourceCodeWriter;


    public ApplicationModuleStruct(ProjectDescription projectDescription,
                                   String moduleName,
                                   IndentingWriterFactory indentingWriterFactory,
                                   InitializrMetadata initializrMetadata) {
        super(projectDescription, moduleName, indentingWriterFactory, initializrMetadata);
        sourceCodeWriter = new JavaSourceCodeWriter(indentingWriterFactory);
    }

    @Override
    public void buildPackage(Path moduleRoot, Path packageRootPath) throws IOException {
        //写一个启动类
        ProjectDescription description = getProjectDescription();
        JavaSourceCode sourceCode = new JavaSourceCode();

        JavaCompilationUnit codeCompilationUnit = sourceCode
                .createCompilationUnit(description.getPackageName(), description.getApplicationName());
        JavaTypeDeclaration starter = codeCompilationUnit.createTypeDeclaration(description.getApplicationName());
        //填充方法
        mainCustom().customize(starter);
        starter.annotate(Annotation.name("com.freemud.eden.core.annotation.EdenApplication"));
        starter.modifiers(Modifier.PUBLIC);

        SourceStructure structure = new SourceStructure(moduleRoot.resolve("src/main/"), new JavaLanguage());
        sourceCodeWriter.writeTo(structure, sourceCode);
    }

    @Override
    public Consumer<MavenBuild> custom() {
        ProjectDescription description = getProjectDescription();
        return mavenBuild -> {
            mavenBuild.dependencies()
                    .add("common", Dependency.withCoordinates(description.getGroupId(), description.getArtifactId() + "-manager").build());
        };
    }

    private MainApplicationTypeCustomizer<JavaTypeDeclaration> mainCustom() {
        return typeDeclaration -> {
            typeDeclaration.modifiers(Modifier.PUBLIC);
            typeDeclaration.addMethodDeclaration(
                    JavaMethodDeclaration.method("main")
                            .modifiers(Modifier.PUBLIC | Modifier.STATIC).returning("void")
                            .parameters(new Parameter("java.lang.String[]", "args"))
                            .body(new JavaExpressionStatement(
                                    new JavaMethodInvocation("org.springframework.boot.SpringApplication", "run",
                                            typeDeclaration.getName() + ".class", "args"))));
        };

    }
}

总结

因为eden目前就一个版本1.0.0,所以还没研究spring-boot-dependencies的版本管理功能; 这是个很大的缺陷,后面再搞

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 效果
    • 代码地址
      • 实现的效果
        • 效果图
        • 依赖jar
        • 核心接口
          • ProjectMetadataController
            • ProjectGenerationController
              • InitializrMetadata
                • ProjectGenerator
                  • ProjectContributor
                  • 个性化调整
                    • EnvironmentExtensionPostProcessor
                      • ExtensionBeanFactoryPostProcessor
                        • ReadMeGenerationConfiguration
                          • ModuleGenerationConfiguration
                            • CustomMavenBuildProjectContributor
                              • ModuleContributor
                                • ModuleStruct
                                • 总结
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档