springinitializr源码分析(骨架生成代码生成器)
2023年7月3日发(作者:)
springinitializr源码分析(⾻架⽣成代码⽣成器)spring initializr 通过web ui/web service api的⽅式,可以快速⽣成spring boot项⽬。github地址:使⽤起来⾮常简单,在页⾯上配置项⽬属性,如项⽬类型、语⾔、spring boot版本、依赖项等,然后点击generate project,即可⽣成⼀个单module的spring boot项⽬。最终⽣成的是⼀个zip包,提供给⽤户下载。在实际应⽤中,可以考虑以spring initializr为原型,对源码进⾏改造,开发属于团队内部的代码⽣成器。这⾥简单对spring initializr源码进⾏分析。spring initializr本⾝也是spring boot项⽬,启动类是 lizrService 。(强烈推荐clone代码到本地,直接启动InitializrService即可,没有其他依赖服务)META-INF/ies 配置如下:AutoConfiguration=nmentPostProcessor=oundryEnvironmentPostProcessor在SpringApplication启动时会主动去加载配置类 InitializrAutoConfiguration,直接看源码:@Configuration@EnableConfigurationProperties()@AutoConfigureAfter({ , , })public class InitializrAutoConfiguration { @Bean @ConditionalOnMissingBean() public InitializrMetadataProvider initializrMetadataProvider( InitializrProperties properties, // 读取前缀为initializr的配置 ObjectMapper objectMapper, RestTemplateBuilder restTemplateBuilder) { InitializrMetadata metadata = InitializrMetadataBuilder
.fromInitializrProperties(properties).build(); // 根据配置⽣成 metadata return new DefaultInitializrMetadataProvider(metadata, objectMapper, ()); }}这⾥的操作是读取前缀为initializr配置项,然后根据配置项⽣成默认的metadata。这个metadata是⽤于⽣成项⽬⽤的源数据,相当于⼀个配置集合。metadata主要定义了以下⼏个属性:env - 环境变量dependencies - 依赖项types - 项⽬类型:maven/gradlepackagings - 打包⽅式:war/jarjavaVersions - jdk版本languages - 使⽤的语⾔:java/kotlin/groovybootVersions - spring boot版本以上是启动过程中完成的⼯作。⽤户的访问操作对应的Controller类为 ntroller。访问主页⾯,即触发执⾏以下代码@RequestMapping(value = "/", produces = "text/html")public String home(Map model) { renderHome(model); return "home";}可以看出这段代码完成的主要逻辑是根据⽤户请求参数,⽣成model对象,再⽤model对象去渲染 , 完成渲染后,再返回给客户端。这⾥主要看渲染的过程,及renderHome()⽅法的实现。/** * Render the home page with the specified template. */ protected void renderHome(Map model) { InitializrMetadata metadata = (); // 获取metadata ("serviceUrl", generateAppUrl()); // 从metadata中读取默认的配置型,并赋值给model对象 BeanWrapperImpl wrapper = new BeanWrapperImpl(metadata); for (PropertyDescriptor descriptor : pertyDescriptors()) { if ("types".equals(e())) { ("types", removeTypes(es())); } else { (e(), pertyValue(e())); } } // Google analytics support ("trackingCode", figuration().getEnv().getGoogleAnalyticsTrackingCode()); }renderHome() ⽅法的处理逻辑很简单,就是把metadata对象的字段读取出来转成map对象赋值给model。前端再根据model对象渲染页⾯,页⾯上项⽬类型、语⾔、spring boot版本、依赖项等可选内容以及项⽬属性的默认填充值都是根据model对象渲染出来的。可见涵盖了的是所有配置项的全集。下⾯介绍点击generate project时,服务做了哪些操作。点击generate project发送的请求如下:进⾏拆分后的参数列表,这些参数光看参数名就可以知道其含义:type=gradle-projectlanguage=javabootVersion=EbaseDir=demogroupId=eartifactId=demoname=demodescription=Demo+project+for+Spring+BootpackageName=ckaging=jarjavaVersion=1.8style=web访问的接⼝是,位于 ntroller#springZip()@RequestMapping("/") @ResponseBody public ResponseEntity springZip(BasicProjectRequest basicRequest) throws IOException { ProjectRequest request = (ProjectRequest) basicRequest; File dir = teProjectStructure(request); // ⽣成代码的关键代码 File download = DistributionFile(dir, ".zip"); String wrapperScript = getWrapperScript(request); new File(dir, wrapperScript).setExecutable(true); Zip zip = new Zip(); ject(new Project()); aultexcludes(false); ZipFileSet set = new ZipFileSet(); (dir); eMode("755"); ludes(wrapperScript); aultexcludes(false); eset(set); set = new ZipFileSet(); (dir); ludes("**,"); ludes(wrapperScript); aultexcludes(false); eset(set); tFile(onicalFile()); e(); return upload(download, dir, generateFileName(request, "zip"), "application/zip"); }springZip主要执⾏的是打包的过程,主要看 teProjectStructure(request),这⼀步是⽣成代码的关键步骤。/** * Generate a project structure for the specified {@link ProjectRequest}. Returns a directory containing the * project. */ public File generateProjectStructure(ProjectRequest request) { try { // 根据请求⽣成model,这⾥的model⾮全集model Map model = resolveModel(request); // ⽣成代码 File rootDir = generateProjectStructure(request, model); publishProjectGeneratedEvent(request); return rootDir; } catch (InitializrException ex) { publishProjectFailedEvent(request, ex); throw ex; } }resolveModel⽅法的代码⾮常长,这⾥就不贴了。处理逻辑不复杂,简单地说就是根据⽤户请求request,以及项⽬启动时⽣成的metadata,⽣成出⼀个model。这⾥的metadata相当于⼀个配置的全集,⽤户请求request指明了项⽬的个性化配置,根据个性化配置,从配置全集中进⾏筛选和组装出model。这个model对象会⽤于下⼀步⽣成代码使⽤。这⾥可以看⼀下model包含了哪些内容,下⾯的截图是我在本地debug时⽣成的。好了。下⾯看下是如何根据model⽣成代码的。/** * Generate a project structure for the specified {@link ProjectRequest} and resolved * model. */ protected File generateProjectStructure(ProjectRequest request, Map model) { File rootDir; try { rootDir = TempFile("tmp", "", getTemporaryDirectory()); } catch (IOException e) { throw new IllegalStateException("Cannot create temp dir", e); } addTempFile(e(), rootDir); (); (); File dir = initializerProjectDir(rootDir, request); if (isGradleBuild(request)) { String gradle = new String(doGenerateGradleBuild(model)); writeText(new File(dir, ""), gradle); writeGradleWrapper(dir, rse(tVersion())); } else { String pom = new String(doGenerateMavenPom(model)); writeText(new File(dir, ""), pom); writeMavenWrapper(dir); } generateGitIgnore(dir, request); String applicationName = licationName(); String language = guage(); String codeLocation = language; File src = new File(new File(dir, "src/main/" + codeLocation), kageName().replace(".", "/")); (); String extension = ("kotlin".equals(language) ? "kt" : language); write(new File(src, applicationName + "." + extension), "Application." + extension, model); if ("war".equals(kaging())) { String fileName = "ServletInitializer." + extension; write(new File(src, fileName), fileName, model); } File test = new File(new File(dir, "src/test/" + codeLocation), kageName().replace(".", "/")); (); setupTestModel(request, model); write(new File(test, applicationName + "Tests." + extension), "ApplicationTests." + extension, model); File resources = new File(dir, "src/main/resources"); (); writeText(new File(resources, "ties"), ""); if (Facet()) { new File(dir, "src/main/resources/templates").mkdirs(); new File(dir, "src/main/resources/static").mkdirs(); } return rootDir; }这段代码虽长,但理解起来也不复杂,根据项⽬的属性model对象,⽣成对应的⽂件夹和⽂件。⽐如src/main/java、src/main/resources、ties等。⽂件夹⽣成直接调⽤()⽅法即可。那么⽂件是如何⽣成的呢?⾸先对于⼀些默认的配置⽂件,⽐如maven/gradle wrapper等,直接拷贝即可。对于⼀些需要定制的⽂件,那么就通过模板渲染的⽅式去⽣成。这⾥的涉及的代码如下:// 写⼊⽂件, target表⽰⽬标⽂件,templateName表⽰模板名称,model表⽰通过解析获得的项⽬配置 public void write(File target, String templateName, Map model) { String body = s(templateName, model); writeText(target, body); } // 根据model渲染模板,⽣成⽂件内容 public String process(String name, Map model) { try { Template template = getTemplate(name); return e(model); } catch (Exception e) { ("Cannot render: " + name, e); throw new IllegalStateException("Cannot render template", e); } } // 定位模板 public Template getTemplate(String name) { if (cache) { return eIfAbsent(name, this::loadTemplate); } return loadTemplate(name); }这⾥举⼀个例⼦。模板,位于/initializr/initializr-generator/src/main/resources/templates/,内容如下:package {{packageName}};import Application;{{applicationImports}}{{applicationAnnotations}}public class {{applicationName}} { public static void main(String[] args) { ({{applicationName}}.class, args); }}这⾥的{{packageName}} {{applicationImports}} 为占位符,通过model中的属性进⾏替换,获得最终的。这⾥的模板使⽤了mustcache框架,跟freemarker有点类似。⽬前initializr提供了以下模板⾄此,代码⽣成的逻辑已经差不多讲完了。简单的说,就是根据⽤户request,结合预设的配置全集metadata⽣成对应的model,再根据model渲染模板,⽣成指定的⽂件和⽂件夹,再打包返回给⽤户。如果要定制代码⽣成器,可以参考这种做法,主要是改造metadata和模板,可以增加或者修改metadata和模板来满⾜定制化的需求。
发布者:admin,转转请注明出处:http://www.yc00.com/news/1688384197a129965.html
评论列表(0条)