基于FreeMarker的自定义代码生成器

我们平时开发一些项目时,可能需要创建很多代码,比如比如Web项目创建的bean,Mybatis项目创建的若干mapper接口文件和mapper映射文件。

在建立这些文件的过程中,我们可以发现,这些接口文件和映射文件从规则上来看基本上都是千篇一律。为此,官方以及民间大佬都提供了一些代码生成器,例如mybatis-generator、Mybatis-plus的代码生成器等,但是可能跟我们平时使用习惯不一样,比如mybatis-generator生成Example类来给数据库查询添加条件,但对于我这种强迫症来说,希望这个名字时Condition之类的描述而非Example。本文将介绍如何使用FreeMarker定义自己的代码生成器

FreeMarker简介

FreeMarker是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。FreeMarker中文官网

最基本的,我们需要有一个通用的模板文件、模板文件中需要个性化输出的数据模型,如下所示。
定义一个HTML模板:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

其中${user}、${latestProduct.name}、${latestProduct.url}等内容就是根据提供的数据模型变化的动态部分。

定义一个数据模型:
数据模型的基本结构是树状的,这棵树可以很复杂,并且可以有很大的深度,我们通常通过Map类型的数据结构存储模型。

(root)
  |
  +- user = "Big Joe"
  |
  +- latestProduct
      |
      +- url = "products/greenmouse.html"
      |
      +- name = "green mouse"
 Map root = new HashMap();
 root.put("user", "Big Joe");
 Map latest = new HashMap();
 root.put("latestProduct", latest);
 latest.put("url", "products/greenmouse.html");
 latest.put("name", "green mouse");

模板和数据模型结合后,生成的HTML文件如下:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome Big Joe!</h1>
  <p>Our latest product:
  <a href="products/greenmouse.html">green mouse</a>!
</body>
</html>

关于如何通过Java将模板和模型整合到一起输出文件,以及FreeMarker的其他语法,大家可参考FreeMarker中文官网,这里不再赘述。

使用FreeMarker生成JavaBean文件

一个最基本的JavaBean文件示例:

public class Label{
	
	private Integer labelId;
	private String labelName;
	
	public void setLabelId(Integer labelId){
		this.labelId = labelId;
	}
	
	public Integer getLabelId(){
		return this.labelId;
	}
	public void setLabelName(String labelName){
		this.labelName = labelName;
	}
	
	public String getLabelName(){
		return this.labelName;
	}
}

数据表列操作工具类ColumnUtil

我们知道数据表的表名、列名、列类型跟JavaBean中的名称和类型是有所差别的,但也有规律可循,我们可以定义工具类ColumnUtil将数据库名称、类型信息转化成符合Java的变量名和变量类型。

我们先定义两个静态数组,分别表示数据库中常用的列类型和JavaBean变量类型。

//数据库中常用的列类型
private static String[] fieldTypes = {"tinyint", "int", "bigint", "varchar", "text", "date", "datetime", "decimal","char"};
//对应的JavaBean变量类型
private static String[] javaTypes = {"Integer", "Integer", "Long", "String", "String", "java.util.Date", "java.util.Date", "Long", "String"};

然后定义一些方法将两者对应起来,这个简单,这里不赘述。

接下来就是列名和表名转化成符合Java规范的格式了,数据库中我们命名往往采用"_"分割,而Java中往往采用驼峰的形式,以下是将表名转化成符合Java规范的示例。

//例如:TEST_JAVA_BEAN表转化成表名TestJavaBean类
public static String getClassName(String tableName){
	String tempTableName = tableName.toLowerCase();
	String[] tempNames = tempTableName.split("_");
	StringBuilder sbud = new StringBuilder();
	for(int i = 0; i < tempNames.length; i ++){
		sbud.append(tempNames[i].substring(0, 1).toUpperCase()).append(tempNames[i].substring(1));
	}
	return sbud.toString();
}	

数据表列名转化同理,但需要注意类名是从第一个单词开始大写,变量是从第二个开始大写,因此有细微差别。

获取列名对应的变量之后,再写两个方法定义setter/getter方法。

public static String getMethodName(String attrName){
	return "get" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1);
}
	
public static String setMethodName(String attrName){
	return "set" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1);
}

数据表列操作工具类ColumnUtil

一个数据库有多张表,一张表也有多个数据列,将数据库内容和Java内容对应之后,我们还得将表信息和列信息存储起来。

我们需要定义一个ColumnInfo存储列信息,定义一个TableInfo来存储表信息,这都非常简单。

public class ColumnInfo {
	
	private String fieldName;//列名
	
	private String fieldType;//列类型
	
	private String javaType;//Java类型
	
	private String getMethodName;//对应getter方法
	
	private String setMethodName;//对应setter方法

	private String comments;//表注释
	
	private boolean isKey;//是否主键
}
//省略setter/getter方法
public class TableInfo {
    private String tableName;//表名
    private List<ColumnInfo> columnInfos;//列信息
    //省略setter/getter方法
}

JavaBean模板

从开头示例我们可以找出哪些是写死在模板中,哪些是需要通过数据模型获取的内容。

  • 类名——数据库的表名
  • 成员变量、setter/getter方法——数据表的列名
  • 变量类型、setter/getter方法返回类型——数据表各列的类型

根据以上分析,我们可以得到以下JavaBean模板:

package ${modelPackage};

public class ${className}{
	
	<#list columns as column>
	private ${column.javaType} ${column.attrName};
	</#list>
	
	<#list columns as column>
	public void ${column.setMethodName}(${column.javaType} ${column.attrName}){
		this.${column.attrName} = ${column.attrName};
	}
	
	public ${column.javaType} ${column.getMethodName}(){
		return this.${column.attrName};
	}
	</#list>
}
  • modelPackage表示包名,可通过配置文件获取
  • className表示表名
  • 通过list(FreeMarker语法,类似与for循环)生成成员变量、setter/getter方法等信息

JDBC操作类获取数据库信息

模板定义好了,数据库信息和Java内容的对应关系也好了,接下来就是获取数据库信息了。因为我们要做的是一个代码器,所以要尽可能复用,我们可以定义一个JDBC操作类JDBCUtil来操作数据库。

以下是JDBCUtil获取表名和各个表列信息的方法,其余注册数据库驱动、连接数据库写在getConnection()中。

	public static List<String> getTables(String database){
        Connection connection = getConnection();
        List<String> tables = new ArrayList<>();
        PreparedStatement pre=null;
        String sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = ? ";
        try {
            pre = connection.prepareStatement(sql);
            pre.setObject(1,database);
            ResultSet res = pre.executeQuery();
            while(res.next()){
                tables.add(res.getString("table_name"));
            }
        } catch (SQLException e) {
            System.out.println("查询"+database+"数据库的数据表失败!");
            e.printStackTrace();
        }finally{
            try {
                if(pre!=null)
                    pre.close();
                if(connection!=null)
                    connection.close();
            } catch (SQLException e) {
                System.out.println("查询"+database+"表名后,关闭数据库连接失败!");
                e.printStackTrace();
            }
        }
        return tables;
    }

    public static TableInfo getTableInfo(String database,String tableName){
        Connection connection = getConnection();
        TableInfo tableInfo = new TableInfo(tableName);
        List<ColumnInfo> columnInfos = new ArrayList<>();
        PreparedStatement pre=null;
        String sql = "SELECT COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT FROM information_schema.COLUMNS T WHERE T.TABLE_SCHEMA=? AND T.TABLE_NAME=?";
        try {
            pre = connection.prepareStatement(sql);
            pre.setObject(1,database);
            pre.setObject(2,tableName);
            ResultSet res = pre.executeQuery();
            while(res.next()){
                String columnName = res.getString("COLUMN_NAME");
                String columnType = res.getString("DATA_TYPE");
                String comments = res.getString("COLUMN_COMMENT");
                String pri = res.getString("COLUMN_KEY");
                ColumnInfo columnInfo = new ColumnInfo(columnName,columnType,comments,pri);
                columnInfos.add(columnInfo);
            }
            tableInfo.setColumnInfos(columnInfos);

        } catch (SQLException e) {
            System.out.println("查询表"+tableName+"信息失败!");
            e.printStackTrace();
        }finally{
            try {
                if(pre!=null)
                    pre.close();
                if(connection!=null)
                    connection.close();
            } catch (SQLException e) {
                System.out.println("查询"+tableName+"表后,关闭数据库连接失败!");
                e.printStackTrace();
            }
        }
        return tableInfo;
    }

使用FreeMarker生成JavaBean

完成了数据模型的获取,模板的定义,最后一步就是生成我们的JavaBean了。

创建Configuration实例

Configuration实例是存储FreeMarker应用级设置的核心部分。同时,它也处理创建和缓存预解析模板(比如Template对象)的工作。因为Configuration是单例的,只需创建一次,所以我定义了一个FreemarkerConfiguration来生成。

public class FreemarkerConfiguration {

    private static Configuration cfg = null;

    static {
        try {
            File resourcesRoot = new File(ResourceConfiguration.getResourceRoot());//ResourceConfiguration.getResourceRoot()是自定义的获取资源文件根目录
            cfg = new Configuration(Configuration.VERSION_2_3_23);
            cfg.setDirectoryForTemplateLoading(resourcesRoot);
            cfg.setDefaultEncoding("UTF-8");
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
        }catch (IOException ioException){
            System.out.println("freemarker配置初始化失败:无法找到模板存放路径");
        }
    }

    public static Configuration getCfg(){
        return cfg;
    }
}

获取数据模型

通过前面的准备工作,我们将数据模型信息都放入Map中。

//获取数据模型,其中blog是博主自己的数据库
public Map createModel() throws IOException, TemplateException {
	List<String> tables = JDBCUtil.getTables("blog");
    for(int i=0;i<tables.size();i++){
    	Map root = new HashMap();
    	TableInfo tableInfo = JDBCUtil.getTableInfo("blog",tables.get(i));
    	root.put("className", ColumnUtil.getClassName(tables.get(i)));
        List<ColumnInfo> columnInfos = tableInfo.getColumnInfos();
        root.put("columns",columnInfos);
        root.put("modelPackage","test");//自定义包名
    }
    return root;
}

className、columns、数据库名、modelPackage和包名等内容如果需要更加个性化一点,不用硬编码,大家可以将这些内容放在配置文件中,读取配置文件,动态修改,这里暂时先不讲。

生成JavaBean

最后生成JavaBean,如下所示。其中templatePath是模板文件的路径,root是建好的数据模型。

public void writerCode(String templatePath,Map root) throws IOException, TemplateException {
	//给Configuration实例设置模板路径
	Template temp = cfg.getTemplate(templatePath);
	//输出后的文件位置,这里演示设置成控制台窗口,正式用的时候可设置成自己的Java包或文件夹
    Writer out = new OutputStreamWriter(System.out);
    //输出内容
    temp.process(root, out);
}

最后效果如下(其中一个表对应的JavaBean):
在这里插入图片描述

总结

大家看过FreeMarker后就知道其实这个Java库上手还是很简单的,很多第三方库都是这样,入门简单,但是如果要将它们用于平时开发中,自己真正能用起来,还是比较麻烦的,主要是细节,比如这次用FreeMarker生成JavaBean,都知道【模板+模型=输出】,但是模板的定义和数据模型的生成中间就有很多细节需要处理,处理好了就是项目,是自己以后能反复使用的工具,处理不好,就是一次普通demo

思路分享

这次FreeMarker生成JavaBean,我的思路如下:

  1. 首先观察一个bean文件,总结哪些是写死的,哪些需要自己动态补充;
  2. 然后写一个基本模板;
  3. 分析模板中的各数据应该如何得到;
  4. 如何用同一套代码获取同类别的多个数据模型(比如本文中的生成工具类等);
  5. 根据模型,修改模板;
  6. 最后实现。

因为时间和篇幅原因,这次只介绍JavaBean的生成,后续将补充Mybatis的mapper接口文件和映射文件等内容的自定义生成,等全部完成之后,会把源码放出来,有兴趣可以对博主关注,如果平时不怎么看CSDN的关注人动态,可关注下面公众号,不定期分享一些小demo、小项目以及小工具的学习心得
秀宇笔记

往期文章:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值