什么样的场景和代码适合用自动生成这种方式呢?
做过Java服务端的朋友一定都知道代码中我们需要编写与数据库表映射的Java实体类(Entity)、需要编写与实体对应的DAO类(XxDao.java类中有包含对应实体的增、删、改、查基本操作)。在这些实体类中通常都是一些属性方法以及属性对应的get/set方法、而实体对应的DAO类中也基本会包含有增、删、改、查这些与数据库操作相关的方法。在编写了那么多的实体类和Dao类的过程中 你是否发现了这些代码中有很多地方都相似或者差不多、只是名字不同而已呢?对、那么这个时候其实我们可以定义一个模板、通过模板我们来让代码自动生成去吧。
对于这个问题 最简洁直接的回答就是:代替手动编写代码、提高工作效率。
在进入正文前,让我们首先简单、快速了解一下FreeMarker。
(做过Web开发的朋友肯定都是相当熟悉的 我当时 也是在做Web开发的时候第一次接触了FreeMarker)
<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>
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();
}
}
}
<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>
/**
* 属性类型枚举类
* @author github.com/zmatsh
*
*/
public enum PropertyType {
Byte, Short, Int, Long, Boolean, Float, Double, String, ByteArray, Date
}
/**
* 实体对应的属性类
* @author github.com/zmatsh
*
*/
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;
}
}
import java.util.List;
/**
* 实体类
* @author github.com/zmatsh
*
*/
public class Entity {
// 实体所在的包名
private String javaPackage;
// 实体类名
private String className;
// 父类名
private String superclass;
// 属性集合
List 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 getProperties() {
return properties;
}
public void setProperties(List properties) {
this.properties = properties;
}
public boolean isConstructors() {
return constructors;
}
public void setConstructors(boolean constructors) {
this.constructors = constructors;
}
}
package ${entity.javaPackage};
/**
* This code is generated by FreeMarker
* @authorgithub.com/zmatsh
*
*/
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>
}
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;
/**
* 自动生成实体类客户端
* @authorgithub.com/zmatsh
*
*/
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 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 createDataModel() {
Map root = new HashMap();
Entity user = new Entity();
user.setJavaPackage("com.study.entity"); // 创建包名
user.setClassName("User"); // 创建类名
user.setConstructors(true); // 是否创建构造函数
// user.setSuperclass("person");
List propertyList = new ArrayList();
// 创建实体属性一
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;
}
}
通过上面两个简单的示例我们了解到所谓的自动生成代码其实就是:
上面的示例中 有的朋友可能会问不就是要编写一个实体对象吗?干嘛搞那么麻烦、又建.ftl文件、又写了那么多类、定义模板数据的过程也是那么麻烦、我还不如手动去写、声明几个属性、set/get快捷键一下子就编写好啦。 真的是这样吗?
从一个辅助工具和软件架构的方面去思考,假设做成一个开发的辅助工具或是插件去完成实体类和对应DAO类的自动生成。假设需要建10个实体类和对应含有增删改查基本操作的DAO类。我在C/S客户端上填写包名、类名、属性字段等信息 然后一键生成,想想那是多么爽、多么痛快的一件事(当然 前提是你的模板类要编写的非常强大、通用),而你也许还在不停的 Ctrl+C、Ctrl+V。