反射+枚舉+freemarker,自動生成實體類,自動建表建索引(一)之生成實體類,枚舉詳解


  用反射+枚舉+freemarker,自己實現的自動生成實體類和自動建立數據表。用enum枚舉作為數據表的配置文件,1個枚舉就是1張表,根據枚舉類,自動生成實體類,和自動建表。下面先介紹自動生成實體類。   主要步驟就是先反射讀取枚舉類,獲取所需信息,然后用freemarker生成實體類。這里需要用到freemarker.jar這個jar包(點擊下載)。

  1、普及下enum枚舉,既然用到了,就要好好深入了解一下。

  其實枚舉是個特殊的java類。創建枚舉類型要使用 enum 關鍵字,隱含了所創建的類型都是 java.lang.Enum 類的子類(java.lang.Enum 是一個抽象類)。枚舉類型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚舉類型的名稱。枚舉類型的每一個值都將映射到 protected Enum(String name, int ordinal) 構造函數中,在這里,每個值的名稱都被轉換成一個字符串,並且序數設置表示了此設置被創建的順序(默認是0,1,2,3...)。   原來定義的常量一般用接口來定義,如下:
public interface IConstants {
String MON = "Mon";
String TUE = "Tue";
String WED = "Wed";
String THU = "Thu";
String FRI = "Fri";
String SAT = "Sat";
String SUN = "Sun";
}


  現在用enum定義如下:
package com.pzb.test;

public enum EnumTest {
MON, TUE, WED, THU, FRI, SAT, SUN;
}


  實際上是調用了如下方法數次:
new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);


  然后是介紹下枚舉常用的方法:
int compareTo(E o) 
//比較此枚舉與指定對象的順序。
Class<E> getDeclaringClass()
//返回與此枚舉常量的枚舉類型相對應的 Class 對象。
String name()
//返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明。
int ordinal()
//返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數為零)。
String toString()
// 返回枚舉常量的名稱,它包含在聲明中。
static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
//返回帶指定名稱的指定枚舉類型的枚舉常量。


  然后是原理分析:enum 的語法結構盡管和 class 的語法不一樣,但是經過編譯器編譯之后產生的是一個class文件。該class文件經過反編譯可以看到實際上是生成了一個類,該類繼承了java.lang.Enum<E>。EnumTest 經過反編譯(javap com.hmw.test.EnumTest 命令)之后得到的內容如下:
public class com.hmw.test.EnumTest extends java.lang.Enum{
public static final com.hmw.test.EnumTest MON;
public static final com.hmw.test.EnumTest TUE;
public static final com.hmw.test.EnumTest WED;
public static final com.hmw.test.EnumTest THU;
public static final com.hmw.test.EnumTest FRI;
public static final com.hmw.test.EnumTest SAT;
public static final com.hmw.test.EnumTest SUN;
static {};
public int getValue();
public boolean isRest();
public static com.hmw.test.EnumTest[] values();
public static com.hmw.test.EnumTest valueOf(java.lang.String);
com.hmw.test.EnumTest(java.lang.String, int, int, com.hmw.test.EnumTest);
}


  所以,實際上 enum 就是一個 class,只不過 java 編譯器幫我們做了語法的解析和編譯而已。可以把 enum 看成是一個普通的 class,它們都可以定義一些屬性和方法,不同之處是:enum 不能使用 extends 關鍵字繼承其他類,因為 enum 已經繼承了 java.lang.Enum(java是單一繼承)。
     

  2、開始用枚舉定義數據表,其重要用到一個自己定義的類,用來作為數據結構。具體看代碼,代碼注釋的也比較詳細了。

package com.test.common;

/**
* 枚舉用到的數據結構,用來存儲表中字段屬性(長度,索引,是否為空,默認值)
* @author Lufeng
*
*/
public class EntityConfigData {
//表中字段屬性名稱常量
public static final String TYPE = "type";
public static final String LENGTH = "length";
public static final String INDEX = "index";
public static final String NULLABLE = "nullable";
public static final String DEFAULTS = "defaults";

//不同類型字段的長度默認值
public static final int TYPE_DEFUALT_INT = 11;
public static final int TYPE_DEFUALT_LONG = 20;
public static final int TYPE_DEFUALT_STRING = 50;

//表中字段屬性(長度,索引,是否為空,默認值)
public Class<?> type;
public int length;
public boolean index;
public boolean nullable;
public String defaults;


public EntityConfigData(Class<?> clazz) {
this(clazz, new Object[0]);
}

public EntityConfigData(Class<?> clazz, Object...params) {
this.type = clazz;

//依次遍歷參數param並賦值
int len = params.length;
for(int i = 0; i < len; i += 2){
Object key = params[i];
Object val = params[i + 1];

//如果是長度
if(LENGTH.equals(key)) {
this.length = Integer.parseInt(val.toString());
} else if (INDEX.equals(key)) {//如果是索引
this.index = Boolean.parseBoolean(val.toString());;
} else if (NULLABLE.equals(key)) {//如果是可否為空
this.nullable =Boolean.parseBoolean(val.toString());
} else if (DEFAULTS.equals(key)) {//如果是默認值
this.defaults = val.toString();
}
}
}
}

package com.test.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 枚舉用到的注解,用來表示將要生成的實體類名,和要建立的數據表名
* @author Lufeng
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface EntityConfig {
String entityName();//實體類名
String tableName();//數據表名
}

package com.test.testentity;

import static com.test.common.EntityConfigData.DEFAULTS;
import static com.test.common.EntityConfigData.INDEX;
import static com.test.common.EntityConfigData.LENGTH;
import static com.test.common.EntityConfigData.NULLABLE;

import com.pwrd.test.EntityConfig;
import com.pwrd.test.EntityConfigData;

/**
* 這個就是主要的枚舉類
* @author Lufeng
*
*/
@EntityConfig(entityName = "Character", tableName = "zh_character")
public enum EntityCharacter {
account(String.class),
name(String.class, LENGTH, 12),
profession(String.class, INDEX, true),
sex(String.class),
level(String.class),
test(String.class, LENGTH, 6, INDEX, true, NULLABLE, true, DEFAULTS, "test"),
testnew(int.class, LENGTH, 6, INDEX, true, DEFAULTS, 100),
;

public EntityConfigData conf;

private EntityCharacter(Class<?> clazz, Object...params) {
conf = new EntityConfigData(clazz, params);
}
}



  3、現在的任務就是反射獲取枚舉類,然后用freemarker生成想要的實體類。

package com.test.common;

import java.io.File;
import java.io.FileOutputStream;
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.Locale;
import java.util.Map;
import java.util.Set;

import com.test.support.PackageClass;

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

/**
* 自動生成實體類的核心類
* @author Lufeng
*
*/
public class GenEntity {
private String pageEncoding = "UTF-8";
private Configuration configuration;//FreeMarker配置

private String sourceDir;//配置源文件夾
private String targetDir;//輸出目標文件夾

/**
* 創建和調整配置,這個在生命周期中只做一次
*/
public GenEntity(String sourceDir, String targetDir) {
this.sourceDir = sourceDir;
this.targetDir = targetDir;
configuration = new Configuration();

try {
String tempDir = System.getProperty("user.dir") + "/src/" + PackageClass.packageToPath(sourceDir) + "/templates";
configuration.setDirectoryForTemplateLoading(new File(tempDir));
configuration.setEncoding(Locale.getDefault(), pageEncoding);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* 生成配置源文件夾下的所有實體類
*/
public void genFiles() {
// 獲取源文件夾下的所有類
Set<Class<?>> sources = PackageClass.find(sourceDir);

// 遍歷所有類,取出有注解的生成實體類
for(Class<?> clazz : sources) {
// 過濾沒有EntityConfig注解的類
if(clazz.isAnnotationPresent(EntityConfig.class)) {
genFile(clazz);
}
}
}

/**
* 生成java實體類的核心方法
* @param clazz
*/
public void genFile(Class<?> clazz) {
try {
genFileHandler(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 生成java實體類的核心方法
* @param clazz
* @throws Exception
*/
private void genFileHandler(Class<?> clazz) throws Exception {
/* 1,獲取模板 */
Template temp = configuration.getTemplate("testEntity.ftl", pageEncoding);
temp.setEncoding(pageEncoding);

/* 2,設置模板的數據內容Map */
// 獲取實體類名,表名,包名
String entityName = GenUtils.getEntityName(clazz);
String tableName = GenUtils.getTableName(clazz);
String packageName = clazz.getPackage().getName();

// 填充Map
Map<String, Object> targetClazz = new HashMap<String, Object>();
List<Map<String, String>> fields = new ArrayList<Map<String, String>>();
targetClazz.put("packageName", packageName);
targetClazz.put("entityName", entityName);
targetClazz.put("tableName", tableName);
targetClazz.put("fields", fields);

// 獲得所有枚舉字段成員(id, account, name, profession...),
// 遍歷每個枚舉成員,獲取屬性,放入Map中
Object[] enums = clazz.getEnumConstants();
for (Object e : enums) {
Map<String, String> field = new HashMap<String, String>();
String name = e.toString();
Class<?> type = (Class<?>) GenUtils.getFieldValue(clazz, e, "type");

field.put("name", name);
field.put("type", type.getSimpleName());

fields.add(field);
}

// 判斷目標文件夾不存在,則新建文件夾
String targetFileDir = this.targetDir + PackageClass.packageToPath(packageName) + "/";
File dir = new File(targetFileDir);
if(!dir.exists()) dir.mkdirs();

/* 3,將模板和數據合並,並生成文件 */
String fileName = targetFileDir + entityName + ".java";
System.out.println("-------開始生成" + fileName + "文件......------");

File target = new File(fileName);
Writer out = new OutputStreamWriter(new FileOutputStream(target), pageEncoding);
temp.process(targetClazz, out);
out.flush();
out.close();

System.out.println("-------" + fileName + "文件生成完畢!-------\n");
}

public static void main(String[] args) {
String dir = System.getProperty("user.dir") + "/../pwvcommon/";
args = new String[] {"com.pwrd.game.testentity", dir};

String sourceDir = args[0];
String targetDir = args[1];

GenEntity generator = new GenEntity(sourceDir, targetDir);
generator.genFiles();

}

}


  生成完畢,截圖如下:


  4、附件

  獲取注解信息的工具類
package com.test.common;

import java.lang.reflect.Field;

/**
* 工具類
* @author Lufeng
*
*/
public class GenUtils {
/**
* 從注解中獲取類名
* @param clazz
* @return
*/
public static String getEntityName(Class<?> clazz) {
EntityConfig config = clazz.getAnnotation(EntityConfig.class);
String entityName = config.entityName();

return entityName;
}

/**
* 從注解中獲取表名
* @param clazz
* @return
*/
public static String getTableName(Class<?> clazz) {
EntityConfig config = clazz.getAnnotation(EntityConfig.class);
String tableName = config.tableName();

return tableName;
}

/**
* 獲取class的object字段的name字段值
* @param clazz
* @param object
* @param name
* @return
* @throws Exception
*/
public static Object getFieldValue(Class<?> clazz, Object object, String name) throws Exception {
//遍歷所有屬性,獲取EntityConfigBase屬性
Field[] fields = object.getClass().getFields();
Field field = null;
for(Field f : fields) {
if(f.get(object) instanceof EntityConfigData) {
field = f;
break;
}
}

//獲取EntityConfigBase中name字段值
if(field == null) return null;

Object obj = field.get(object);
Object result = obj.getClass().getField(name).get(obj);

return result;
}
}

  模板testEntity.ftl內容:
package com.test.entity;

import com.test.MysqlRecord;
import com.test.common.DbField;

public class ${entityName} extends EntityBase {
@Override
public String getTableName() {
return "${tableName?lower_case}";
}

public ${entityName}() {
super();
}

public ${entityName}(MysqlRecord record) {
super(record);
}

public long getId() {
return record.get("id");
}

public void setId(long id) {
return record.set("id", id);
}

<#list fields as field>
<#-- 根據不同的類型,設置不同的方法名
<#if field.type == "int">
<#assign getMethod = "getI32">
<#assign setMethod = "setI32">
<#elseif field.type == "long">
<#assign getMethod = "getI64">
<#assign setMethod = "setI64">
<#elseif field.type == "String">
<#assign getMethod = "getStr">
<#assign setMethod = "setString">
</#if>
-->
public ${field.type} get${field.name?cap_first}() {
return record.${getMethod}(${enumName}.${field.name}.ordinal());
}

public void set${field.name?cap_first}(${field.type} ${field.name}) {
record.${setMethod}(${enumName}.${field.name}.ordinal(), ${field.name});
}

</#list>

}


具體的自動建表,詳見下一篇博文:反射+枚舉+freemarker,自動生成實體類,自動建表建索引(二)之建表建索引,注解和DatabaseMetaData獲取信息。 注:這個是本人原創的,如需轉載,請尊重勞動果實,務必保持原鏈接(http://blog.csdn.net/lufeng20/article/details/8730604)

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com