Java進階之 如何自動生成程式碼

小呂-ICE發表於2014-12-27
一、前言:為什麼要有程式碼的自動生成?
    對於這個問題 最簡潔直接的回答就是:代替手動編寫程式碼、提高工作效率。

    什麼樣的場景和程式碼適合用自動生成這種方式呢?
    做過Java服務端的朋友一定都知道程式碼中我們需要編寫與資料庫表對映的Java實體類(Entity)、需要編寫與實體對應的DAO類(XxDao.java類中有包含對應實體的增、刪、改、查基本操作)。在這些實體類中通常都是一些屬性方法以及屬性對應的get/set方法、而實體對應的DAO類中也基本會包含有增、刪、改、查這些與資料庫操作相關的方法。在編寫了那麼多的實體類和Dao類的過程中 你是否發現了這些程式碼中有很多地方都相似或者差不多、只是名字不同而已呢?對、那麼這個時候其實我們可以定義一個模板、通過模板我們來讓程式碼自動生成去吧。

二、FreeMarker的簡單介紹
   在進入正文前,讓我們首先簡單、快速瞭解一下FreeMarker。
  (做過Web開發的朋友肯定都是相當熟悉的、小呂當時 也是在做Web開發的時候第一次接觸了FreeMarker)
   1、概述:FreeMarker是一款模板引擎:即一種基於模板、用來生成輸出文字的通用工具。更多的是被用來設計生成HTML頁面。

   簡單說就是:FreeMarker是使用模板生成文字頁面來呈現已經準備好的資料。如下圖表述

                                                 

   FreeMarker官網:http://freemarker.org/
   
   2、通過一個簡單的例子來展示如何使用FreeMarker定義模板、繫結模型資料、生成最終顯示的Html頁面:
     1>.新建專案 在專案根目錄下新建"template"資料夾,用來存放我們的Template file,

如我們新建模板檔案test.ftl  內容如下:

<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>
Welcome ${user}<#if user == "Big Joe">, our beloved leader</#if>!
</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

    2>.專案引入freemarker.jar(下載地址:https://jarfiles.pandaidea.com/freemarker.html),
   在Java類中使用FreeMarker API方法引用模板檔案、建立資料模型、合併資料模型與模板檔案最終輸入,

程式碼如下:

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

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

public class HtmlGeneratorClient {

	public static void main(String[] args) {
		try {
			Configuration cfg = new Configuration();
			// 指定模板檔案從何處載入的資料來源,這裡設定成一個檔案目錄
			cfg.setDirectoryForTemplateLoading(new File("./template"));
			cfg.setObjectWrapper(new DefaultObjectWrapper());
			
			// 獲取或建立模板
			Template template = cfg.getTemplate("test.ftl");
			
			// 建立資料模型
			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");
			
			// 將模板和資料模型合併 輸出到Console
			Writer out = new OutputStreamWriter(System.out);
			template.process(root, out);
			out.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TemplateException e) {
			e.printStackTrace();
		}

	}

}

   3>.最終生成的HTML的頁面程式碼如下:

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

三、如何使用FreeMerker完成Java類程式碼的自動生成
   上面示例 我們的ftl模板檔案定義的是HTML頁面模板,那麼我們將ftl模板定義為Java程式碼呢 通過資料模板的繫結不就可以生成Java類啦,
   下面小呂將利用模板來自動建立實體物件的java類(編寫實體類的模板檔案相對邏輯簡單,但簡單歸簡單,最重要的還是我們要掌握它的思想)
   1、屬性型別的列舉類 PropertyType.java

/**
 * 屬性型別列舉類
 * @author  lvzb.software@qq.com
 *
 */
public enum PropertyType {
	Byte, Short, Int, Long, Boolean, Float, Double, String, ByteArray, Date
}
   2、實體對應的欄位屬性類 Property.java
/**
 * 實體對應的屬性類
 * @author  lvzb.software@qq.com
 *
 */
public class Property {
	// 屬性資料型別
	private String javaType;
	// 屬性名稱
	private String propertyName;
	
	private PropertyType propertyType;
	
	public String getJavaType() {
		return javaType;
	}

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

	public String getPropertyName() {
		return propertyName;
	}

	public void setPropertyName(String propertyName) {
		this.propertyName = propertyName;
	}

	public PropertyType getPropertyType() {
		return propertyType;
	}

	public void setPropertyType(PropertyType propertyType) {
		this.propertyType = propertyType;
	}
		
}
   3、實體模型類 Entity.java
import java.util.List;

/**
 * 實體類
 * @author  lvzb.software@qq.com
 *
 */
public class Entity {
	// 實體所在的包名
	private String javaPackage;
	// 實體類名
	private String className;
	// 父類名
	private String superclass;
	// 屬性集合
	List<Property> properties;
	// 是否有建構函式
	private boolean constructors;	
	
	public String getJavaPackage() {
		return javaPackage;
	}
	
	public void setJavaPackage(String javaPackage) {
		this.javaPackage = javaPackage;
	}
	
	public String getClassName() {
		return className;
	}
	
	public void setClassName(String className) {
		this.className = className;
	}
	
	public String getSuperclass() {
		return superclass;
	}
	
	public void setSuperclass(String superclass) {
		this.superclass = superclass;
	}
	
	public List<Property> getProperties() {
		return properties;
	}
	
	public void setProperties(List<Property> properties) {
		this.properties = properties;
	}
	
	public boolean isConstructors() {
		return constructors;
	}
	
	public void setConstructors(boolean constructors) {
		this.constructors = constructors;
	}	

}
   4、在專案根目錄下新建"template"資料夾,用來存放我們的Template file, 新建實體模板entity.ftl 內容如下:
package ${entity.javaPackage};

/**
 * This code is generated by FreeMarker
 * @author lvzb.software@qq.com
 *
 */
public class ${entity.className}<#if entity.superclass?has_content> extends ${entity.superclass} </#if>
{
    /********** attribute ***********/
<#list entity.properties as property>
    private ${property.javaType} ${property.propertyName};
    
</#list>
    /********** constructors ***********/
<#if entity.constructors>
    public ${entity.className}() {
    
    }

    public ${entity.className}(<#list entity.properties as property>${property.javaType} ${property.propertyName}<#if property_has_next>, </#if></#list>) {
    <#list entity.properties as property>
        this.${property.propertyName} = ${property.propertyName};
    </#list>
    }
</#if>

    /********** get/set ***********/
<#list entity.properties as property>
    public ${property.javaType} get${property.propertyName?cap_first}() {
        return ${property.propertyName};
    }

    public void set${property.propertyName?cap_first}(${property.javaType} ${property.propertyName}) {
        this.${property.propertyName} = ${property.propertyName};
    }
    
</#list>
}
    5、自動生成實體類 客戶端程式碼 EntityGeneratorClient.java
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
 * 自動生成實體類客戶端
 * @author lvzb.software@qq.com
 *
 */
public class EntityGeneratorClient {
	
	private static File javaFile = null;

	public static void main(String[] args) {
		Configuration cfg = new Configuration();	
		try { 
			// 步驟一:指定 模板檔案從何處載入的資料來源,這裡設定一個檔案目錄
			cfg.setDirectoryForTemplateLoading(new File("./template"));
			cfg.setObjectWrapper(new DefaultObjectWrapper());
			
			// 步驟二:獲取 模板檔案
			Template template = cfg.getTemplate("entity.ftl");
			
			// 步驟三:建立 資料模型
			Map<String, Object> root = createDataModel();
			
			// 步驟四:合併 模板 和 資料模型
			// 建立.java類檔案
			if(javaFile != null){
				Writer javaWriter = new FileWriter(javaFile);
				template.process(root, javaWriter);
				javaWriter.flush();
				System.out.println("檔案生成路徑:" + javaFile.getCanonicalPath());
				
				javaWriter.close();
			}
			// 輸出到Console控制檯
			Writer out = new OutputStreamWriter(System.out);
			template.process(root, out);
			out.flush();
			out.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TemplateException e) {
			e.printStackTrace();
		}

	}

	
	/**
	 * 建立資料模型
	 * @return
	 */
	private static Map<String, Object> createDataModel() {
		Map<String, Object> root = new HashMap<String, Object>();
		Entity user = new Entity();
		user.setJavaPackage("com.study.entity"); // 建立包名
		user.setClassName("User");  // 建立類名
		user.setConstructors(true); // 是否建立建構函式
		// user.setSuperclass("person");
		
		List<Property> propertyList = new ArrayList<Property>();
		
		// 建立實體屬性一 
		Property attribute1 = new Property();
		attribute1.setJavaType("String");
		attribute1.setPropertyName("name");
		attribute1.setPropertyType(PropertyType.String);
		
		// 建立實體屬性二
		Property attribute2 = new Property();
		attribute2.setJavaType("int");
		attribute2.setPropertyName("age");
		attribute2.setPropertyType(PropertyType.Int);
		
		propertyList.add(attribute1);
		propertyList.add(attribute2);
		
		// 將屬性集合新增到實體物件中
		user.setProperties(propertyList);
		
		// 建立.java類檔案
		File outDirFile = new File("./src-template");
		if(!outDirFile.exists()){
			outDirFile.mkdir();
		}
		
		javaFile = toJavaFilename(outDirFile, user.getJavaPackage(), user.getClassName());
		
		root.put("entity", user);
		return root;
	}
	
	
	/**
	 * 建立.java檔案所在路徑 和 返回.java檔案File物件
	 * @param outDirFile 生成檔案路徑
	 * @param javaPackage java包名
	 * @param javaClassName java類名
	 * @return
	 */
	private static File toJavaFilename(File outDirFile, String javaPackage, String javaClassName) {
        String packageSubPath = javaPackage.replace('.', '/');
        File packagePath = new File(outDirFile, packageSubPath);
        File file = new File(packagePath, javaClassName + ".java");
        if(!packagePath.exists()){
        	packagePath.mkdirs();
        }
        return file;
    }

}
    6、執行程式 我們將會在專案根目錄下 生成資料夾 src-template以及自動生成的實體類User.java
    效果圖如下:

       --- 執行後 --->

     <程式執行前目錄結構>                                                <程式執行後目錄結構>

    自動生成的實體類User.java 程式碼如下:   

package com.study.entity;

/**
 * This code is generated by FreeMarker
 * @author lvzb.software@qq.com
 *
 */
public class User
{
    /********** attribute ***********/
    private String name;
    
    private int age;
    
    /********** constructors ***********/
    public User() {
    
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /********** get/set ***********/
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
}


四、背後的思考

    通過上面兩個簡單的示例我們瞭解到所謂的自動生成程式碼其實就是:

    1、定義java類别範本檔案 2、定義模板資料  3、引用模板檔案(.ftl)與模板資料合併生成Java類

    上面的示例中 有的朋友可能會問不就是要編寫一個實體物件嗎?幹嘛搞那麼麻煩、又建.ftl檔案、又寫了那麼多類、定義模板資料的過程也是那麼麻煩、我還不如手動去寫、宣告幾個屬性、set/get快捷鍵一下子就編寫好啦。 真的是這樣嗎?
    從一個輔助工具和軟體架構的方面去思考,假設做成一個開發的輔助工具或是外掛去完成實體類和對應DAO類的自動生成。假設需要建10個實體類和對應含有增刪改查基本操作的DAO類。我在C/S客戶端上填寫包名、類名、屬性欄位等資訊 然後一鍵生成,想想那是多麼爽、多麼痛快的一件事(當然 前提是你的模板類要編寫的非常強大、通用),而你也許還在不停的 Ctrl+C、Ctrl+V。

五、其他
   關於如何編寫.ftl模板檔案、就需要自己去翻閱資料自我學習啦!

   小呂還是提供FreeMarker官網:http://freemarker.org/

   最後小呂 附上本篇的原始碼下載地址:http://download.csdn.net/detail/l416112167/8305899


六、擴充套件學習

       使用 Velocity 模板引擎快速生成程式碼:[非常值得擴充套件學習]

       http://www.ibm.com/developerworks/cn/java/j-lo-velocity1/index.html




相關文章