第二章代码生成
- 互联网
- 2025-08-23 13:57:01

第二章 代码生成 前言
本笔记主要用途是学习up 主程序员鱼皮的项目:代码生成器时的一些学习心得。
代码地址: github /Liucc-123/yuzi-generator.git 项目教程: .codefather /course/1790980795074654209
本节重点本节属于项目的第一阶段:开发本地代码生成器。重点内容包括:
项目初始化。静态文件生成。动态文件代码生成。FreeMarker 模板引擎入门及实战。动静结合 - ACM 示例项目模板代码生成。 一、项目初始化 初始化根目录使用 IDEA创建一个干净的文件夹 yuzi-generator 作为整个项目的根目录。
使用 Git 管理项目,建议在项目根目录中初始化 Git 仓库。
忽略无用提交使用 .gitignore 文件忽略项目中不需要提交的文件(如IDE自动生成的工程文件)。
推荐使用IDE插件(如.ignore插件)生成.gitignore文件,并手动添加需要忽略的目录和文件。
忽略文件常见配置项设置
### Custom template .idea target yuzi-generator.iml .DS_Store ### Java template # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http:// .java /en/download/help/error_hotspot.xml hs_err_pid* replay_pid* ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: intellij-support.jetbrains /hc/en-us/articles/206544839 # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # AWS User-specific .idea/**/aws.xml # Generated files .idea/**/contentModel.xml # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/artifacts # .idea/compiler.xml # .idea/jarRepositories.xml # .idea/modules.xml # .idea/*.iml # .idea/modules # *.iml # *.ipr # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # SonarLint plugin .idea/sonarlint/ # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser如果文件已被Git跟踪,需执行git rm -rf --cached .命令取消跟踪。
创建 Demo 示例代码工程 新建 yuzi-generator-demo-projects 目录,存放所有示例代码。下载并复制 ACM 示例模板代码到该目录下(可通过云盘下载)。 创建本地代码生成器项目在项目根目录下新建 yuzi-generator-basic 项目,使用Maven管理项目。
JDK选择1.8,取消Git仓库勾选(因为外层已托管)。
在项目的 pom.xml 文件中引入 Hutool、Apache Commons Collections、Lombok 和 JUnit 等依赖。
<dependencies> <!-- doc.hutool / --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> <!-- mvnrepository /artifact/org.apache mons/commons-collections4 --> <dependency> <groupId>org.apache mons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency> <!-- projectlombok.org/ --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> 二、实现流程目标:制作本地代码生成器,根据用户输入生成不同的 ACM 示例代码模板。
需求拆解 将需求拆解为“生成静态文件”和“生成动态文件”两个步骤。静态文件:直接复制,不做改动(如 README.md)。动态文件:基于模板文件和用户输入生成代码(如 MainTemplate.java)。 实现步骤 生成静态文件(通过 Main 方法运行)。生成动态文件(通过 Main 方法运行)。同时生成静态和动态文件,得到完整代码。开发命令行工具,接受用户输入并生成代码。将工具封装为 jar 包和脚本,供用户调用。 三、静态文件生成静态文件是指直接复制、不做任何改动的文件。
现成的工具库复制目录
使用 Hutool 的 FileUtil.copy 方法,一行代码实现目录复制。
示例代码:
public static void copyFilesByHutool(String inputPath, String outputPath) { FileUtil.copy(inputPath, outputPath, false); }递归遍历
手动编写递归算法,逐个复制目录和文件。
示例代码:
public static void copyFilesByRecursive(String inputPath, String outputPath) { File inputFile = new File(inputPath); File outputFile = new File(outputPath); try { copyFileByRecursive(inputFile, outputFile); } catch (Exception e) { System.err.println("文件复制失败"); e.printStackTrace(); } } /** * 文件 A => 目录 B,则文件 A 放在目录 B 下 * 文件 A => 文件 B,则文件 A 覆盖文件 B * 目录 A => 目录 B,则目录 A 放在目录 B 下 * * 核心思路:先创建目录,然后遍历目录内的文件,依次复制 * @param inputFile * @param outputFile * @throws IOException */ private static void copyFileByRecursive(File inputFile, File outputFile) throws IOException { // 区分是文件还是目录 if (inputFile.isDirectory()) { System.out.println(inputFile.getName()); File destOutputFile = new File(outputFile, inputFile.getName()); // 如果是目录,首先创建目标目录 if (!destOutputFile.exists()) { destOutputFile.mkdirs(); } // 获取目录下的所有文件和子目录 File[] files = inputFile.listFiles(); // 无子文件,直接结束 if (ArrayUtil.isEmpty(files)) { return; } for (File file : files) { // 递归拷贝下一层文件 copyFileByRecursive(file, destOutputFile); } } else { // 是文件,直接复制到目标目录下 Path destPath = outputFile.toPath().resolve(inputFile.getName()); Files.copy(inputFile.toPath(), destPath, StandardCopyOption.REPLACE_EXISTING); } } 四、动态文件生成思路动态文件是指需要根据用户输入生成的文件。
明确动态生成需求 增加作者注释(如 @author)。修改程序输出信息。支持循环读取输入或单次读取。 动态生成的核心原理 使用模板引擎(如 FreeMarker)实现动态内容生成。提前编写模板文件,通过用户输入的参数替换模板中的占位符。 五、FreeMarker 模板引擎入门FreeMarker 是一个开源模板引擎,用于生成动态内容。
官方手册:http://freemarker.foofun /
模板引擎的作用
提供模板文件语法和解析能力。将数据和模板分离,便于开发和维护。模板
使用 FTL(FreeMarker Template Language)编写模板文件。包含文本、插值(${...})、FTL 指令(<#xxx>)和注释(<#-- ... -->)。数据模型
为模板准备的数据,可以是 Java 对象或 HashMap。Demo 实战
引入 FreeMarker 依赖:
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.32</version> </dependency>创建配置对象、加载模板、创建数据模型、指定输出路径并生成文件。
示例代码:
Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); configuration.setDirectoryForTemplateLoading(new File("src/main/resources/templates")); configuration.setDefaultEncoding("utf-8"); Template template = configuration.getTemplate("myweb.html.ftl"); Map<String, Object> dataModel = new HashMap<>(); dataModel.put("currentYear", 2023); Writer out = new FileWriter("myweb.html"); template.process(dataModel, out); out.close();常用语法
插值:${变量}。分支:<#if condition>...</#if>。默认值:${变量!默认值}。循环:<#list items as item>...</#list>。宏定义:<#macro name>...</#macro>。内建函数:变量?方法。 六、动态文件生成实现实现 ACM 示例模板项目的动态生成。
定义数据模型
创建 MainTemplateConfig 类,定义模板所需的参数(如作者、是否循环、输出信息)。
示例代码
/** * 动态模板配置 */ @Data public class MainTemplateConfig { /** * 作者注释信息 */ private String author = "liucc"; // 默认值 /** * 是否生成循环 */ private boolean isLoop; /** * 输出信息 */ private String outputText = "求和结果:"; }编写动态模板
在 resources/templates 目录下创建 MainTemplate.java.ftl 文件。
使用 FreeMarker 语法编写模板,例如:
package com.yupi.acm; /** * ACM 输入模板(多数之和) * @author ${author} */ public class MainTemplate { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); <#if loop> while (scanner.hasNext()) { </#if> int n = scanner.nextInt(); int[] arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = scanner.nextInt(); } int sum = 0; for (int num : arr) { sum += num; } System.out.println("${outputText}" + sum); <#if loop> } </#if> scanner.close(); } }组合生成
在 DynamicGenerator 类中实现生成逻辑。
示例代码:
public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException { Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); File templateDir = new File(inputPath).getParentFile(); configuration.setDirectoryForTemplateLoading(templateDir); configuration.setDefaultEncoding("utf-8"); String templateName = new File(inputPath).getName(); Template template = configuration.getTemplate(templateName); Writer out = new FileWriter(outputPath); template.process(model, out); out.close(); }完善优化
给字符串变量设置默认值,避免模板生成时出错。抽取生成逻辑为方法,提高代码复用性。 七、动静结合 - 生成完整代码将静态文件生成和动态文件生成结合,生成完整的 ACM 示例代码。
核心代码生成器(静态+动态文件生成)
package com.liucc; import com.liucc.model.MainTemplateConfig; import freemarker.template.TemplateException; import java.io.File; import java.io.IOException; /** * 核心模板生成器(静态+动态) */ public class MainGenerator { public static void main(String[] args) throws TemplateException, IOException { MainTemplateConfig model = new MainTemplateConfig(); model.setAuthor("liucc"); model.setLoop(true); model.setOutputText("求和结果:"); doGenerate(model); } public static void doGenerate(Object model) throws TemplateException, IOException { String projectPath = System.getProperty("user.dir"); // /Users/liuchuangchuang/code/yuzi-generator/yuzi-generator-basic String parentPath = new File(projectPath).getParentFile().getAbsolutePath(); // 输入路径 String inputPath = parentPath + File.separatorChar + "yuzi-generator-demo-projects/acm-template"; String outputPath = projectPath; // 生成静态文件 StaticGenerator.copyFilesByRecursive(inputPath, outputPath); String dynamicInputPath = projectPath + File.separatorChar + "src/main/resources/templates/MainTemplate.java.ftl"; String dynamicOutputPath = projectPath + File.separatorChar + "MainTemplate.java"; // 生成动态文件 DynamicGenerator.doGenerate(dynamicInputPath, dynamicOutputPath, model); } }静态文件生成器
package com.liucc; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ArrayUtil; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; public class StaticGenerator { public static void main(String[] args) { // 获取整个项目的根路径 String projectPath = System.getProperty("user.dir"); File parentFile = new File(projectPath).getParentFile(); // 输入路径:ACM 示例代码模板目录 String inputPath = new File(parentFile, "yuzi-generator-demo-projects/acm-template").getAbsolutePath(); // 输出路径:直接输出到项目的根目录 String outputPath = projectPath; copyFilesByRecursive(inputPath, outputPath); } /** * 拷贝文件(Hutool 实现,会将输入目录完整拷贝到输出目录下) *Ω * @param inputPath * @param outputPath */ public static void copyFilesByHutool(String inputPath, String outputPath) { FileUtil.copy(inputPath, outputPath, false); } /** * 递归拷贝文件(递归实现,会将输入目录完整拷贝到输出目录下) * @param inputPath * @param outputPath */ public static void copyFilesByRecursive(String inputPath, String outputPath) { File inputFile = new File(inputPath); File outputFile = new File(outputPath); try { copyFileByRecursive(inputFile, outputFile); } catch (Exception e) { System.err.println("文件复制失败"); e.printStackTrace(); } } /** * 文件 A => 目录 B,则文件 A 放在目录 B 下 * 文件 A => 文件 B,则文件 A 覆盖文件 B * 目录 A => 目录 B,则目录 A 放在目录 B 下 * * 核心思路:先创建目录,然后遍历目录内的文件,依次复制 * @param inputFile * @param outputFile * @throws IOException */ private static void copyFileByRecursive(File inputFile, File outputFile) throws IOException { // 区分是文件还是目录 if (inputFile.isDirectory()) { System.out.println(inputFile.getName()); File destOutputFile = new File(outputFile, inputFile.getName()); // 如果是目录,首先创建目标目录 if (!destOutputFile.exists()) { destOutputFile.mkdirs(); } // 获取目录下的所有文件和子目录 File[] files = inputFile.listFiles(); // 无子文件,直接结束 if (ArrayUtil.isEmpty(files)) { return; } for (File file : files) { // 递归拷贝下一层文件 copyFileByRecursive(file, destOutputFile); } } else { // 是文件,直接复制到目标目录下 Path destPath = outputFile.toPath().resolve(inputFile.getName()); Files.copy(inputFile.toPath(), destPath, StandardCopyOption.REPLACE_EXISTING); } } }动态模板文件生成
package com.liucc; import com.liucc.model.MainTemplateConfig; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; /** * 动态模板文件生成 */ public class DynamicGenerator { public static void main(String[] args) throws IOException, TemplateException { // 获取整个项目的根路径 String projectPath = System.getProperty("user.dir"); System.out.println("user.dir:" + projectPath); // 输入路径:FTL 示例代码模板目录 String inputPath = projectPath + File.separatorChar + "src/main/resources/templates/MainTemplate.java.ftl"; // 输出路径:直接输出到项目的根目录 String outputPath = projectPath + File.separatorChar + "MainTemplate.java"; // 读取模板配置 MainTemplateConfig config = new MainTemplateConfig(); config.setAuthor("liucc"); config.setLoop(true); config.setOutputText("求和结果:"); // 生成模板 doGenerate(inputPath, outputPath, config); } /** * * @param inputPath 模板读取路径 * @param outputPath 动态模板生成路径 * @param model 数据模型 * @throws IOException * @throws TemplateException */ public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException { // 1、new 出 Configuration 对象,参数为 FreeMarker 版本号 Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); File templateFile = new File(inputPath).getParentFile(); // 指定模板文件所在的路径 configuration.setDirectoryForTemplateLoading(templateFile); // 设置模板文件使用的字符集 configuration.setDefaultEncoding("utf-8"); // 数字格式设置 configuration.setNumberFormat("0.##########"); // 2、创建模板对象,加载指定模板 String templateName = new File(inputPath).getName(); Template template = configuration.getTemplate(templateName); // 3、创建数据模型 ==> model // 4、生成 Writer out = new FileWriter(outputPath); template.process(model, out); // 生成文件后别忘了关闭哦 out.close(); } }