敏捷开发之通用代码生成器

282 阅读4分钟

Free marker 模板引擎介绍

free marker 是一款模板引擎,通过模板生成文件、包括 HTML,excel、Java代码等。可以极大的提高开发效率。它的逻辑主要写在自定义标签中。

集成 freemarker 依赖

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.32</version>
</dependency>

写一个最简单的代码生成器(写啥生成啥)

  • 定义模板文件 test.ftl
package nuc.zm.generator.test;

public class Test{
/**
* ID
*/
private Integer id;
}
  • 书写 Java 工具类
package nuc.zm.generator.test;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class TestUtil {
    private static String ftlPath = "generator/src/main/java/nuc/zm/generator/test";
    // 注意 这里一定要由 / 结尾 要不然生成的 文件名 为 testTest.java 噶
    private static String toPath = "generator/src/main/java/nuc/zm/generator/test/";

    public static void main(String[] args) throws IOException, TemplateException {
        // 读模板 这四步都读模板是固定写法
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);
        // 设置模板引擎的模板文件在的目录。
        configuration.setDirectoryForTemplateLoading(new File(ftlPath));
        // 设置对象包装器
        configuration.setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_2_3_32));
        // 读模板路径下的具体文件
        Template template = configuration.getTemplate("test.ftl");

        // 生成 Java文件
        FileWriter fileWriter = new FileWriter(toPath + "Test.java");
        BufferedWriter writer = new BufferedWriter(fileWriter);
        template.process(null,writer<p align=left>);</p>
        writer.flush();
        writer.close();
    }
}
  • 运行 工具类就会自动生成 Test.java 类 image.png

部分代码固定,封装为工具类

package nuc.zm.generator.util;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 代码生成器工具类
 * 模板引擎 free marker
 * @author zm
 * @date 2023/05/10
 */
public class FreemarkerUtils {
    private static final String ftlPath = "generator/src/main/java/nuc/zm/generator/ftl/";
    static Template template ;

    /**
     * 初始化配置
     *
     * @param ftlName ftl名字
     * @throws IOException ioexception
     */
    public static void initConfig(String ftlName) throws IOException {
        // 读模板 这四步都读模板是固定写法
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);
        // 设置模板引擎的模板文件在的目录。
        configuration.setDirectoryForTemplateLoading(new File(ftlPath));
        // 设置对象包装器
        configuration.setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_2_3_32));
        // 读模板路径下的具体文件
        template = configuration.getTemplate(ftlName);
    }

    /**
     * 发电机
     *
     * @param fileName 包含路径的文件名称
     * @throws IOException       IoException
     * @throws TemplateException 模板异常
     */
    public static void generator(String fileName) throws IOException, TemplateException {
        FileWriter fileWriter = new FileWriter(fileName);
        BufferedWriter writer = new BufferedWriter(fileWriter);
        template.process(null,writer);
        writer.flush();
        writer.close();
    }
}

复制以前写过的Javaservice层单表查询代码置模板文件中

将变化部分封装为free marker 变量

例如 :将 Chapter 变为 Domain大小写要对应。chapter变为{Domain} 大小写要对应。 chapter 变为 {domain} 大小写要对应。

image.png

最终工具类

public class FreemarkerUtils {
    private static final String ftlPath = "generator/src/main/java/nuc/zm/generator/ftl/";
    static Template template ;

    /**
     * 初始化配置
     *
     * @param ftlName ftl名字
     * @throws IOException ioexception
     */
    public static void initConfig(String ftlName) throws IOException {
        // 读模板 这四步都读模板是固定写法
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);
        // 设置模板引擎的模板文件在的目录。
        configuration.setDirectoryForTemplateLoading(new File(ftlPath));
        // 设置对象包装器
        configuration.setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_2_3_32));
        // 读模板路径下的具体文件
        template = configuration.getTemplate(ftlName);
    }

    /**
     * 发电机
     *
     * @param fileName 包含路径的文件名称
     * @throws IOException       IoException
     * @throws TemplateException 模板异常
     */
    public static void generator(String fileName , Map<String,String> map) throws IOException, TemplateException {
        FileWriter fileWriter = new FileWriter(fileName);
        BufferedWriter writer = new BufferedWriter(fileWriter);
        // 第一个参数放置模型数据
        template.process(map,writer);
        writer.flush();
        writer.close();
    }


}

service 生成主类

public class GeneratorMain {
    private static final String  toServicePath = "your service interface dir path";
    private static final String  toServiceImplPath = "your serviceImpl class dir path";
    private static final String toControllerPath = moudle + "your controller dir path";
    // 变量都抽离出来即可。
    public static void main(String[] args) throws IOException, TemplateException {
        Map<String,String> map = new HashMap<>();
        Scanner sc = new Scanner(System.in);
        String Domain = sc.next();
        String domain = sc.next();
        map.put("Domain",Domain);
        map.put("domain",domain);
        // 接口生成器
        FreemarkerUtils.initConfig("service.ftl");
        FreemarkerUtils.generator(toServicePath + Domain +  "Service.java" , map);
        FreemarkerUtils.initConfig("service_impl.ftl");
        // 实现类生成器
        FreemarkerUtils.generator(toServiceImplPath + Domain + "ServiceImpl.java",map);
    }
}

DTO、POJO类的生成 (freemarker 标签 list 的使用)

这个就比较复杂了 要知道的信息如下

  • 数据库表的字段
  • 要映射成的Java类型
  • 字段大小驼峰的转换 : course_id --> courseId | CourseId

解决数据库字段的映射问题

定义映射字段类

package nuc.zm.generator.util;

/**
 * 数据库字段映射类
 *
 * @author zm
 * @date 2023/05/11
 */
public class DbField {
    private String name; // 数据库字段名
    private String nameHump; // 转为小驼峰
    private String nameBigHump; // 转为大驼峰
    private String nameCn; // 中文名 非中文 搁置即可
    private String dbType; // 数据库字段类型 ru bigint
    private String javaType; // java类型
    private String comment; // 字段备注

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameHump() {
        return nameHump;
    }

    public void setNameHump(String nameHump) {
        this.nameHump = nameHump;
    }

    public String getNameBigHump() {
        return nameBigHump;
    }

    public void setNameBigHump(String nameBigHump) {
        this.nameBigHump = nameBigHump;
    }

    public String getNameCn() {
        return nameCn;
    }

    public void setNameCn(String nameCn) {
        this.nameCn = nameCn;
    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }

    public String getJavaType() {
        return javaType;
    }

    public void setJavaType(String javaType) {
        this.javaType = javaType;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public String toString() {
        return "DbField{" +
                "name='" + name + ''' +
                ", nameHump='" + nameHump + ''' +
                ", nameBigHump='" + nameBigHump + ''' +
                ", nameCn='" + nameCn + ''' +
                ", dbType='" + dbType + ''' +
                ", javaType='" + javaType + ''' +
                ", comment='" + comment + ''' +
                '}';
    }
}

数据库工具类 连接和映射

package nuc.zm.generator.util;

import java.io.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 数据库工具类
 * 获取数据库所有字段名 映射 为 java 类型
 *
 * @author zm
 * @date 2023/05/11
 */
public class DbUtils {

    public static final String jdbcProPath = "generator/src/main/resources/jdbc.properties";


    private static Connection getConnection() throws IOException, SQLException, ClassNotFoundException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(jdbcProPath));
        Properties properties = new Properties();
        properties.load(bufferedInputStream);
        String driver = (String) properties.get("jdbc.driver");
        String url = properties.getProperty("jdbc.url");
        String username = properties.getProperty("jdbc.username");
        String passwd = properties.getProperty("jdbc.passwd");
        Class.forName(driver);
        return DriverManager.getConnection(url, username, passwd);
    }

    /**
     * 得到表名 注释为表名
     *
     * @param tableName 表名
     * @return {@link String}
     * @throws SQLException           sqlexception异常
     * @throws IOException            ioexception
     * @throws ClassNotFoundException 类没有发现异常
     */
    public static String getTableComment(String tableName) throws SQLException, IOException, ClassNotFoundException {
        Connection connection = getConnection();
        Statement statement = connection.createStatement();
        ResultSet rs = statement.executeQuery("select table_comment from information_schema.TABLES where TABLE_NAME = '" + tableName + "'");
        if (rs != null) {
           while (rs.next()){
               String tableNameCn = rs.getString("table_comment");
               System.out.println("表的中文名称" + tableNameCn);
               close(connection,statement,rs);
               return tableNameCn;
           }
        }
        return null;
    }

    public static List<DbField> getAllFieldInfo(String tableName) throws SQLException, IOException, ClassNotFoundException {
        Connection connection = getConnection();
        Statement statement = connection.createStatement();
        ArrayList<DbField> ans = new ArrayList<>();
        ResultSet rs = statement.executeQuery("show full columns from " + tableName );
        if (rs != null) {
            while (rs.next()) {
                String fieldName = rs.getString("field");
                String dBType = rs.getString("type");
                String comment = rs.getString("comment");
                DbField dbField = new DbField();
                dbField.setName(fieldName);
                dbField.setDbType(dBType);
                dbField.setComment(comment);
                if (comment.contains("|")) {
                    dbField.setNameCn(comment.substring(0,comment.indexOf("|")));
                }
                dbField.setNameHump(toNameHump(fieldName));
                dbField.setNameBigHump(toBigNameHump(fieldName));
                ans.add(dbField);
            }
        }
        return ans;
    }

    /**
     * 大名字驼峰
     * 规定列名 为 xx_xx
     * @param fieldName 字段名
     * @return {@link String}
     */
    private static String toBigNameHump(String fieldName) {
        if (fieldName == null || fieldName.isEmpty()) {
            return "";
        }
        // 开头是否大写
        boolean toUpper = true;
        return connectStr(fieldName,toUpper);

    }

    private static String toNameHump(String fieldName) {
        if (fieldName == null || fieldName.isEmpty()) {
            return "";
        }
        // 开头是否大写
        boolean toUpper = false;
        return connectStr(fieldName,toUpper);
    }

    private static void close(Connection con, Statement st, ResultSet rs) {
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private static String connectStr(String fieldName , boolean toUpper) {
        StringBuilder sb = new StringBuilder(fieldName.length());
        for (char ch : fieldName.toCharArray()) {
            if (ch == '_') {
                toUpper = true;
                continue;
            }
            sb.append(toUpper ? Character.toUpperCase(ch) : ch);
            toUpper = false;
        }
        return sb.toString();
    }

}

最终的代码生成器工具类

/**
 * 代码生成器工具类
 * 模板引擎 free marker
 * @author zm
 * @date 2023/05/10
 */
public class FreemarkerUtils {
    private static final String ftlPath = "generator/src/main/java/nuc/zm/generator/ftl/";
    static Template template ;

    /**
     * 初始化配置
     *
     * @param ftlName ftl名字
     * @throws IOException ioexception
     */
    public static void initConfig(String ftlName) throws IOException {
        // 读模板 这四步都读模板是固定写法
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);
        // 设置模板引擎的模板文件在的目录。
        configuration.setDirectoryForTemplateLoading(new File(ftlPath));
        // 设置对象包装器
        configuration.setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_2_3_32));
        // 读模板路径下的具体文件
        template = configuration.getTemplate(ftlName);
    }

    /**
     * 发电机
     *
     * @param fileName 包含路径的文件名称
     * @throws IOException       IoException
     * @throws TemplateException 模板异常
     */
    public static void generator(String fileName , Map<String,Object> map) throws IOException, TemplateException {
        FileWriter fileWriter = new FileWriter(fileName);
        BufferedWriter writer = new BufferedWriter(fileWriter);
        // 第一个参数放置模型数据
        template.process(map,writer);
        writer.flush();
        writer.close();
    }