文档资料和参考
https://docs.spring.io/initializr/docs/current/reference/html/
这是一个用于快速生成spring项目的工程,从github下载源码即可运行https://github.com/spring-io/start.spring.io/tree/main/start-site/src,运行效果:https://start.spring.io/
页面渲染所需要的数据源修改非常简单,只需要修改application.yml配置文件即可.
项目主要接口有两个,位于:依赖项io.spring.initializr:initializr-web的jar包中.
分别是位于ProjectGenerationController类的"/starter.zip"和位于ProjectMetadataController类的"/metadata/client"
@RequestMapping("/starter.zip") public ResponseEntity<byte[]> springZip(R request) throws IOException { 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"); }
@RequestMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.2+json" }) public ResponseEntity<String> serviceCapabilitiesV22() { return serviceCapabilitiesFor(InitializrMetadataVersion.V2_2); }
1.数据源通过InitializrAutoConfiguration类加载配置文件到源数据的InitializrProperties配置类
@Configuration @EnableConfigurationProperties(InitializrProperties.class) @AutoConfigureAfter({ JacksonAutoConfiguration.class, RestTemplateAutoConfiguration.class }) public class InitializrAutoConfiguration {
2.当打包请求发出后,会根据用户request和加载到的源数据去生成项目描述ProjectDescription
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(description, request)); addTempFile(result.getRootDirectory(), result.getRootDirectory()); return result; } catch (ProjectGenerationException ex) { publishProjectFailedEvent(request, metadata, ex); throw ex; } }
因为ProjectGenerationController是被@ConditionalOnMissingBean修饰的,所以可以通过@Bean的方式,重写这个类实现自定义入参到项目描述ProjectDescription的转换
3.自定义application配置文件,pom文件或者代码生成方式有很多,pom文件我使用的是实现BuildCustomizer<Build>接口,重写其中的customize(Build build)方法,代码我通过模板去实现,
配置文件通过去覆盖io.spring.initializr.generator.spring.configuration包下的类去实现自己想要的效果(原本的扩展太好)
以swagger为例:加载顺序为spring.factories-->DependencyProjectGenerationConfiguration -->SwaggerBuildCustomizer
@ProjectGenerationConfiguration public class DependencyProjectGenerationConfiguration {
@Bean @ConditionalOnRequestedDependency("swagger") public SwaggerBuildCustomizer springSwaggerBuildCustomizer(ProjectDescription description, TemplateRenderer templateRenderer) { return new SwaggerBuildCustomizer(description, templateRenderer); }
}
@Log4j2 public class SwaggerBuildCustomizer implements BuildCustomizer<Build>, ProjectContributor { private final CustomMutableProjectDescription description; private final TemplateRenderer templateRenderer; private final String SOURCE_PATH = "src/main/java/"; @Override public void customize(Build build) { if (build.dependencies().has("swagger")) { build.dependencies().add("swagger-ui", Dependency.withCoordinates("io.springfox", "springfox-swagger-ui") .version(build.dependencies().get("swagger").getVersion())); } } @Override public int getOrder() { return BuildCustomizer.super.getOrder(); } public SwaggerBuildCustomizer(ProjectDescription description, TemplateRenderer templateRenderer) { this.description = (CustomMutableProjectDescription) description; this.templateRenderer = templateRenderer; } @Override public void contribute(Path projectRoot) throws IOException { log.info("swagger 扩展加载"); Map<String, Object> data = new HashMap<>(); String groupId = description.getGroupId(); String artifactId = description.getArtifactId(); String pre = SOURCE_PATH + groupId.replace(".", "/") + "/" + artifactId; data.put("groupId", groupId); data.put("artifactId", artifactId); data.put("package", groupId + "." + artifactId); data.put("description", description.getSwaggerParams().getDescription()); data.put("title", description.getSwaggerParams().getTitle()); data.put("version", description.getSwaggerParams().getVersion()); Files.createDirectories(projectRoot.resolve(pre + "/config/")); Path codeFile = Files.createFile(projectRoot.resolve(pre + "/config/SwaggerConfig.java")); String code = this.templateRenderer.render("SwaggerConfig.java", data); try (PrintWriter codeWriter = new PrintWriter(Files.newBufferedWriter(codeFile));) { codeWriter.print(code); } } }
package {{package}}.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.spring.web.plugins.Docket; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket customDocket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors .basePackage("{{package}}")) .paths(PathSelectors.any()).build(); } private ApiInfo apiInfo(){ Contact contact = new Contact("author", "https://confluence.enncloud.cn/#all-updates", "author@ennew.cn"); return new ApiInfoBuilder() .title("{{title}}") .description("{{description}}") .contact(contact) .version("{{version}}") .build(); } }
application配置文件自定义扩展核心类如下,我是根据io.spring.initializr.generator.spring.scm.git包下的逻辑进行重写的
@ProjectGenerationConfiguration public class ApplicationConfigurationProjectGenerationConfiguration { public ApplicationConfigurationProjectGenerationConfiguration() { } @Bean public ApplicationPropertiesContributor applicationPropertiesContributor(ApplicationProperties properties) { return new ApplicationPropertiesContributor(properties); } @Bean public WebFoldersContributor webFoldersContributor(Build build, InitializrMetadata metadata) { return new WebFoldersContributor(build, metadata); } @Bean public ApplicationProperties applicationProperties(ObjectProvider<ApplicationPropertiesCustomizer> applicationPropertiesCustomizers) { ApplicationProperties applicationProperties = this.createApplicationProperties(); applicationPropertiesCustomizers.orderedStream().forEach((customizer) -> { customizer.customize(applicationProperties); }); return applicationProperties; } private ApplicationProperties createApplicationProperties() { ApplicationProperties applicationProperties = new ApplicationProperties(); Map stringConfig = new HashMap<String ,String>(); stringConfig.put("server.port","8080"); applicationProperties.getSpring().add(stringConfig); return applicationProperties; } }
4.其他自定义:
plugins自定义:实现implements BuildCustomizer<MavenBuild>接口
重写:
@Override public void customize(MavenBuild build) { if (build.dependencies().has("myBatisGenerator")){ build.dependencies().remove("myBatisGenerator"); build.plugins().add("org.mybatis.generator", "mybatis-generator-maven-plugin",(plugin) -> plugin .dependency("mysql","mysql-connector-java","${mysql.version}")); } }
主类自定义注解:实现implements MainApplicationTypeCustomizer接口
重写:
@Override public void customize(TypeDeclaration typeDeclaration) { String groupId = description.getGroupId(); String artifactId = description.getArtifactId(); String path = groupId + "." + artifactId; String value = path+".dao"; Annotation annotation =Annotation.name("org.mybatis.spring.annotation.MapperScan", (ann) -> ann.attribute("basePackages",String.class,value)); typeDeclaration.annotate(annotation); }