反射+枚举+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,自动生成实体类,自动建表(二)。 注:这个是本人原创的,如需转载,请尊重劳动果实,务必保持原链接(http://blog.csdn.net/lufeng20/article/details/8730604)。
您的回应...

相关话题

查看全部

也许你感兴趣

换一批

热门标签

更多