第二章 代码生成

第二章 代码生成

前言

本笔记主要用途是学习up 主程序员鱼皮的项目:代码生成器时的一些学习心得。

代码地址:https://github.com/Liucc-123/yuzi-generator.git

项目教程:https://www.codefather.cn/course/1790980795074654209

本节重点

本节属于项目的第一阶段:开发本地代码生成器。重点内容包括:

  1. 项目初始化。
  2. 静态文件生成。
  3. 动态文件代码生成。
  4. FreeMarker 模板引擎入门及实战。
  5. 动静结合 - ACM 示例项目模板代码生成。

一、项目初始化

  1. 初始化根目录

    • 使用 IDEA创建一个干净的文件夹 yuzi-generator 作为整个项目的根目录。

    • 使用 Git 管理项目,建议在项目根目录中初始化 Git 仓库。

  2. 忽略无用提交

    • 使用 .gitignore 文件忽略项目中不需要提交的文件(如IDE自动生成的工程文件)。

    • 推荐使用IDE插件(如.ignore插件)生成.gitignore文件,并手动添加需要忽略的目录和文件。

    • 忽略文件常见配置项设置

      xml 复制代码
      ### 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://www.java.com/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: https://intellij-support.jetbrains.com/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 .命令取消跟踪。

  3. 创建 Demo 示例代码工程

    • 新建 yuzi-generator-demo-projects 目录,存放所有示例代码。
    • 下载并复制 ACM 示例模板代码到该目录下(可通过云盘下载)。
  4. 创建本地代码生成器项目

    • 在项目根目录下新建 yuzi-generator-basic 项目,使用Maven管理项目。

    • JDK选择1.8,取消Git仓库勾选(因为外层已托管)。

    • 在项目的 pom.xml 文件中引入 Hutool、Apache Commons Collections、Lombok 和 JUnit 等依赖。

      xml 复制代码
      <dependencies>
          <!-- https://doc.hutool.cn/ -->
          <dependency>
              <groupId>cn.hutool</groupId>
              <artifactId>hutool-all</artifactId>
              <version>5.8.16</version>
          </dependency>
          <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
          <dependency>
              <groupId>org.apache.commons</groupId>
              <artifactId>commons-collections4</artifactId>
              <version>4.4</version>
          </dependency>
          <!-- https://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 示例代码模板。

  1. 需求拆解
    • 将需求拆解为"生成静态文件"和"生成动态文件"两个步骤。
    • 静态文件:直接复制,不做改动(如 README.md)。
    • 动态文件:基于模板文件和用户输入生成代码(如 MainTemplate.java)。
  2. 实现步骤
    • 生成静态文件(通过 Main 方法运行)。
    • 生成动态文件(通过 Main 方法运行)。
    • 同时生成静态和动态文件,得到完整代码。
    • 开发命令行工具,接受用户输入并生成代码。
    • 将工具封装为 jar 包和脚本,供用户调用。

三、静态文件生成

静态文件是指直接复制、不做任何改动的文件。

  1. 现成的工具库复制目录

    • 使用 Hutool 的 FileUtil.copy 方法,一行代码实现目录复制。

    • 示例代码:

      java 复制代码
      public static void copyFilesByHutool(String inputPath, String outputPath) {
          FileUtil.copy(inputPath, outputPath, false);
      }
  2. 递归遍历

    • 手动编写递归算法,逐个复制目录和文件。

    • 示例代码:

      java 复制代码
      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);
          }
      }

四、动态文件生成思路

动态文件是指需要根据用户输入生成的文件。

  1. 明确动态生成需求
    • 增加作者注释(如 @author)。
    • 修改程序输出信息。
    • 支持循环读取输入或单次读取。
  2. 动态生成的核心原理
    • 使用模板引擎(如 FreeMarker)实现动态内容生成。
    • 提前编写模板文件,通过用户输入的参数替换模板中的占位符。

五、FreeMarker 模板引擎入门

FreeMarker 是一个开源模板引擎,用于生成动态内容。

官方手册:http://freemarker.foofun.cn/

  1. 模板引擎的作用

    • 提供模板文件语法和解析能力。
    • 将数据和模板分离,便于开发和维护。
  2. 模板

    • 使用 FTL(FreeMarker Template Language)编写模板文件。
    • 包含文本、插值(${...})、FTL 指令(<#xxx>)和注释(<#-- ... -->)。
  3. 数据模型

    • 为模板准备的数据,可以是 Java 对象或 HashMap。
  4. Demo 实战

    • 引入 FreeMarker 依赖:

      xml 复制代码
      <dependency>
          <groupId>org.freemarker</groupId>
          <artifactId>freemarker</artifactId>
          <version>2.3.32</version>
      </dependency>
    • 创建配置对象、加载模板、创建数据模型、指定输出路径并生成文件。

    • 示例代码:

      java 复制代码
      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();
  5. 常用语法

    • 插值:${变量}
    • 分支:<#if condition>...</#if>
    • 默认值:${变量!默认值}
    • 循环:<#list items as item>...</#list>
    • 宏定义:<#macro name>...</#macro>
    • 内建函数:变量?方法

六、动态文件生成实现

实现 ACM 示例模板项目的动态生成。

  1. 定义数据模型

    • 创建 MainTemplateConfig 类,定义模板所需的参数(如作者、是否循环、输出信息)。

    • 示例代码

      java 复制代码
      /**
       * 动态模板配置
       */
      @Data
      public class MainTemplateConfig {
      
          /**
           * 作者注释信息
           */
          private String author = "liucc"; // 默认值
      
          /**
           * 是否生成循环
           */
          private boolean isLoop;
      
          /**
           * 输出信息
           */
          private String outputText = "求和结果:";
      }
  2. 编写动态模板

    • resources/templates 目录下创建 MainTemplate.java.ftl 文件。

    • 使用 FreeMarker 语法编写模板,例如:

      java 复制代码
      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();
          }
      }
  3. 组合生成

    • DynamicGenerator 类中实现生成逻辑。

    • 示例代码:

      java 复制代码
      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();
      }
  4. 完善优化

    • 给字符串变量设置默认值,避免模板生成时出错。
    • 抽取生成逻辑为方法,提高代码复用性。

七、动静结合 - 生成完整代码

将静态文件生成和动态文件生成结合,生成完整的 ACM 示例代码。

  • 核心代码生成器(静态+动态文件生成)

    java 复制代码
    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);
        }
    }
  • 静态文件生成器

    java 复制代码
    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);
            }
        }
    
    }
  • 动态模板文件生成

    java 复制代码
    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();
        }
    }
相关推荐
猎人everest37 分钟前
SpringBoot应用开发入门
java·spring boot·后端
山猪打不过家猪3 小时前
ASP.NET Core Clean Architecture
java·数据库·asp.net
AllowM3 小时前
【LeetCode Hot100】除自身以外数组的乘积|左右乘积列表,Java实现!图解+代码,小白也能秒懂!
java·算法·leetcode
不会Hello World的小苗3 小时前
Java——列表(List)
java·python·list
二十七剑4 小时前
jvm中各个参数的理解
java·jvm
东阳马生架构6 小时前
JUC并发—9.并发安全集合四
java·juc并发·并发安全的集合
计算机小白一个6 小时前
蓝桥杯 Java B 组之岛屿数量、二叉树路径和(区分DFS与回溯)
java·数据结构·算法·蓝桥杯
菠菠萝宝6 小时前
【Java八股文】10-数据结构与算法面试篇
java·开发语言·面试·红黑树·跳表·排序·lru
不会Hello World的小苗6 小时前
Java——链表(LinkedList)
java·开发语言·链表
Allen Bright7 小时前
【Java基础-46.3】Java泛型通配符详解:解锁类型安全的灵活编程
java·开发语言