使用Freemarker模板自動生成代碼


前言

因感概業務代碼存在大量的增刪改查功能,只是針對不同的表數據而已。故想有沒有辦法,在重復的相同代碼中替換某些內容,於是按此思路搜尋,了解到有freemarker這個東西,一番學習后,特記錄於此。

Freemarker簡介

FreeMarker是一款模板引擎: 一種基於模板和要改變的數據,並用來生成輸出文本(HTML網頁、電子郵件、配置文件、源代碼等)的通用工具。即:輸出=模板+數據。簡單來說,其用法原理類似String的replace方法,或MessageFormat的format方法,都是在一定的代碼中改變(替換)某些內容。不過FreeMarker更加強大,模板來源可以是外部文件或字符串格式,替換的數據格式多樣,而且支持邏輯判斷,如此替換的內容將更加靈活。

小例子

比如我們要產生實體類,我們將模板定義如下,模板文件名為entity.flt(flt是freemarker模板的后綴名)

package com.tenny.${interfaceName?lower_case}.entity;

import java.util.Date;

public class ${entityName} {

<#list params as param>
	// ${param.fieldNote}
    private ${param.fieldType} ${param.fieldName};

</#list>
<#list params as param>
	public void set${param.fieldName?cap_first}(${param.fieldType} ${param.fieldName}){
        this.${param.fieldName} = ${param.fieldName};
    }

    public ${param.fieldType} get${param.fieldName?cap_first}(){
        return this.${param.fieldName};
    }

</#list>
}

其中的${xxx}就是變量了,由外部傳入。可以看到模板還有類似java for each循環的語法。

接下來我們要填充數據了。數據來源后面詳細說,先假定我們有了數據。其格式為Map,內容為:

Map<String, Object> beanMap = new HashMap<String, Object>();
beanMap.put("beanName", "User");// 實體類名
beanMap.put("interfaceName", "User");// 接口名
List<Map<String, String>> paramsList = new ArrayList<Map<String, String>>();
for (int i = 0; i < 4; i++) {
      Map<String, String> tmpParamMap = new HashMap<String, String>();
      tmpParamMap.put("fieldNote", "fieldNote" + i);
      tmpParamMap.put("fieldType", "String");
      tmpParamMap.put("fieldName", "fieldName" + i);
      paramsList.add(tmpParamMap);
}
beanMap.put("params", paramsList);

最后我們使用模板替換數據(這里我的模板放在resources/model/下,目標文件放在resources/class/下):

Configuration config = new Configuration();
config.setObjectWrapper(new DefaultObjectWrapper());
Template template = config.getTemplate("src/main/resources/model/entity.ftl", "UTF-8");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("src/main/resources/class/User.java"), "UTF-8"));
template.process(rootMap, out);
out.flush();
out.close();

這樣最后生成的User.java的內容大概如下:

package com.tenny.user.entity;

import java.util.Date;

public class User {

	// 主鍵id
    private Integer id;

	// 用戶名
    private String username;

	// 密碼
    private String password;

	// 用戶類型#1、管理員;2、普通用戶
    private Integer type;

	// 創建時間
    private Date createtime;

	public void setId(Integer id){
        this.id = id;
    }

    public Integer getId(){
        return this.id;
    }

	public void setUsername(String username){
        this.username = username;
    }

    public String getUsername(){
        return this.username;
    }

	public void setPassword(String password){
        this.password = password;
    }

    public String getPassword(){
        return this.password;
    }

	public void setType(Integer type){
        this.type = type;
    }

    public Integer getType(){
        return this.type;
    }

	public void setCreatetime(Date createtime){
        this.createtime = createtime;
    }

    public Date getCreatetime(){
        return this.createtime;
    }

}

補充

其實上面已經基本將Freemarker的用法展現了。但是我們還可以做更多改進。

使用數據庫作為數據來源

模板中要替換的數據,我們可以從外部手動輸入,然后由程序拼裝成模板引擎需要的格式。但因為我們最開始也說了,大部分相同的是增刪改查,不同的是業務數據庫表,所以我們可以直接將數據庫表字段作為數據來源。

假如你使用的是mysql,可以寫一個mysql工具類,獲取數據庫字段信息

package utils;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MysqlUtil {
	private static String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static String DATABASE_URL = "jdbc:mysql://localhost:3306/news";
    private static String DATABASE_URL_PREFIX = "jdbc:mysql://";
    private static String DATABASE_USER = "root";
    private static String DATABASE_PASSWORD = "tenny123";
    private static Connection con = null;
    private static DatabaseMetaData dbmd = null;

    /**
     * 初始化數據庫鏈接
     * @param db_url 數據庫地址(ex: localhost:3306/news)
     * @param db_user 用戶名
     * @param db_pw 密碼
     */
    public static void init(String db_url, String db_user, String db_pw){
    	try {
    		DATABASE_URL = DATABASE_URL_PREFIX + db_url;
    		DATABASE_USER = db_user;
    		DATABASE_PASSWORD = db_pw;
    		Class.forName(DRIVER_CLASS);
    		con = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
    		dbmd = con.getMetaData();
		} catch (Exception e) {
			e.printStackTrace();
		}
    }

    /**
     * 獲取當前用戶所有表
     * @return
     */
    public static List<String> getTables(){
    	List<String> tables = new ArrayList<String>();

    	try {
			ResultSet rs = dbmd.getTables(null, DATABASE_USER, null, new String[] { "TABLE" });
			while (rs.next()) {
				tables.add(rs.getString("TABLE_NAME"));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}

    	return tables;
    }

    /**
     * 獲取表字段信息
     * @param tableName
     * @return
     */
    public static List<Map<String,String>> getTableCloumns(String tableName){
    	List<Map<String,String>> columns = new ArrayList<Map<String,String>>();
    	try{
            Statement stmt = con.createStatement();

            String sql = "select column_name, data_type, column_key, is_nullable, column_comment from information_schema.columns where table_name='" + tableName + "'and table_schema='" + DATABASE_URL.substring(DATABASE_URL.lastIndexOf("/") + 1, DATABASE_URL.length()) + "'";
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()){
                HashMap<String,String> map = new HashMap<String,String>();
                map.put("columnName", rs.getString("column_name"));
                map.put("dataType", rs.getString("data_type"));
                map.put("isKey", ParamUtil.isEmpty(rs.getString("column_key"))?"false":"true");
                map.put("notNull", rs.getString("is_nullable").equals("YES")?"false":"true");
                map.put("comment", rs.getString("column_comment"));
                columns.add(map);
            }
        }
        catch (SQLException e){
            e.printStackTrace();
        }finally{
        	if(null != con){
	            try {
	        		con.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
        	}
        }
		return columns;
    }

}

獲取了數據表字段信息,再加上自己需要的一些屬性,就可以給模板引擎生成最終代碼文件了。

將freemarker轉為字符串模板

還可以在一開始就將模板數據讀取出來,存放在內存中。

/**
	 * 將模板內容轉成字符串
	 * @param modle 模板名【eg:entity.ftl】
	 * @return
	 */
	public static String file2Str(String modle){
		StringBuffer buffer = new StringBuffer();
		try {
			BufferedReader br = new BufferedReader(new FileReader("/WEB-INF/templates/" + model));
			String line = "";
			while((line = br.readLine()) != null){
				buffer.append(line).append(System.getProperty("line.separator"));// 保持原有換行格式
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return buffer.toString();
	}

當然,利用字符串模板填充數據的寫法有些不同

Configuration config  = new Configuration();
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("entity", "this is a template of ${entityName}");
config.setTemplateLoader(stringTemplateLoader);

StringWriter stringWriter = new StringWriter();
Template template = config.getTemplate("entity", "UTF-8");
Writer out = new BufferedWriter(stringWriter);
template.process(rootMap, out);
out.flush();
out.close();
return stringWriter.toString();

這樣可以將其輸入到html頁面觀看。如果需要的話,還可以隨時修改模板,后台接收前端的字符串模板。如此,模板和數據都可以隨意定制,豈不是更加靈活方便。

其他

有了以上的認知,我們可以做一個頁面,輸入數據庫參數后,選中某個表,就可以生成關於改表的增刪改查代碼。或者在頁面上添加字段,用這些字段來生成代碼。這個就可以自由發揮了~

帖幾張我自己的頁面截圖(沒啥樣式,吃藕勿噴)
首頁
我已經將模板轉換成字符串輸出了,這樣方便查看(其實直接看模板文件也一樣= =!)
模板字符串
點擊獲取表,后台就根據數據庫屬性查詢列表,然后我們選擇某一個表,前端自動填充"實體名"和"接口名"屬性,一般情況無需修改,點擊提交,當當~我們的代碼就出來了
代碼

當然還可以自定義字段,比如我們不選表,也就是說不用數據庫作為來源,在頁面上添加幾個字段
添加字段1

添加字段2

如果你也使用spring mvc,可以和我一樣,設定好多個模板后,一次性將controller-service-serviceImpl-dao-entity-mapping-test全部產生。后面甚至可以繼續設定目錄或包,產生的代碼就可以直接使用啦。

總結

嗯,一句話,模板+數據=最終代碼。

模板根據目標代碼制定,數據來源我們根據實際情況獲取,如此便可diy自己的autocode。

我的autocode地址:https://github.com/tenny-peng/autocode

參考鏈接:http://www.cnblogs.com/yejg1212/p/4322452.html
     http://blog.csdn.net/xiekuntarena/article/details/53032907
     http://blog.csdn.net/5iasp/article/details/27181365


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM