公司在扩张阶段没有制定统一的开发规范, 不同小组之间不存在技术交流,导致了一些问题:
有一次spring-boot的版本升级耗时了半年, 并且相同的坑总是会在不同的小组内重复踩坑; 在一次公司大的调整中推出了脚手架的功能,通过spring-boot的自动装配机制将公司的依赖和默认配置再封一层; 基于公司的情况推出了eden(只是一个名字)框架,将常用依赖分组,通过自动装配机制做成start的形式进行依赖,然后到了现在做的事情: 做一个脚手架页面, 实现一个如spring和ali一样的脚手架功能
<dependency>
<groupId>io.spring.initializr</groupId>
<artifactId>initializr-generator-spring</artifactId>
</dependency>
<dependency>
<groupId>io.spring.initializr</groupId>
<artifactId>initializr-web</artifactId>
</dependency>
项目元信息接口, 效果图1和图2中, 构建一个项目的一些配置和依赖都是以该controller为入口;
比如: #serviceCapabilitiesV21 是idea创建项目时请求的方法
项目生成的controller , 终究是要生成一份的目录,根据不同的请求会打包成对应的格式
比如: #springZip 生成一份zip;如果是idea会自动解压一下
初始元信息,控制了很多行为, 通过InitializrProperties将配置化的数据进行绑定,我们的配置化信息都是存储在这里面
项目自动生成器,内置了一个上下文,也就是说每一次请求都会创建独属于该请求的应用上下文, 将请求的信息 ProjectDescription 和项目贡献组件(@ProjectGenerationConfiguration标注的类) 加入到上下文中,进行项目的构建
这个上下文springcloud的bootstrap context一样, 临时干个活,干完就销毁;
项目贡献者 , 给一个path路径,不同的Contributor按照各自的功能往该路径下进行贡献; 可以说自定义脚手架就是项目贡献者的增增减减
有一些调整是采用简单粗暴的方式,优先实现当前的需求为主,不过有了
官方代码的application.yml文件配置太多,导致我刚开始没细看.后来又绕回来看这个文件的配置
那么我就直接把这个文件拆了一下
通过 EnvironmentExtensionPostProcessor 来保证项目能够正常读取这些配置
有一些ProjectContributor不想使用,因为上下文是内置的,因此我们在refresh阶段对Bean进行调整, 将不需要使用的Bean排除掉,
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 {
}
}
添加README.md可能是最简单的自定义配置了; 参考了一下mvnw和mvnw.cmd的添加,将已经存在的文件复制一份到ROOT文件目录下(即:Project下面), 代码示例:
@ProjectGenerationConfiguration
public class ReadMeGenerationConfiguration {
@Bean
public ProjectContributor readmeProjectContributor(){
return new MultipleResourcesProjectContributor("contributor/readme");
}
}
模块化的结构,是我本次做的最大的调整, 在ExtensionBeanFactoryPostProcessor中我已经将一部分Contributor给排除了; 然后这里再新增一个模块化的Contributor
@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);
}
}
父pom需要支持eden-parent为父依赖, 并且需要支持moudule模块,项目的依赖由子模块引入, 基本将MavenBuildWriter
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标签
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的打印功能
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来提供这方面的功能
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();
}
}
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);
}
}
}
}
不同的module都具有不同的特性; 先拿三个模块试试手
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模块核心将依赖添加
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依赖
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;并拥有一个启动类
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 删除。