也谈代码生成器--JAVA
个人兴趣之余想到的一个代码生成器,简单用代码实现了一下,供大家参考,大家如果有兴趣可以一起来讨论一下。
对于目的:我觉得代码生成器主要就是为了生成一些重复性的功能代码,有可能的话,可以把相关配置一起完成;包括生成代码,以及更新代码。
生成器我觉得不止是生成代码,还要维护代码生成,也许增加了一个字段或者什么情况,也能同样生成代码,便于使用。
我的生成器基于springmvc(annotation),包括jsp+controllor+service+dao+entity
想法是通过对Entity的配置,通过生成引擎生成代码。因此包括几部分,Entity Annotation的定义及使用,引擎,模板。
生成的内容包括JSP,Controllor,Service,Dao
1.Entity Annotation的定义
1: /**
2: * 页面将要展示哪些field,[<b>须定义在属性上,类上</b>],暂时支持@notNull,@NotBank,@Size,3: * 已支持@column要nullable,size属性,所以可以不重复定义,这里指客户端校验,如果要服务端校验,那么加上@NotNull4: * @author xiayong5: *6: */7: @Target({ FIELD, TYPE })8: @Retention(RUNTIME)9: public @interface FieldView {10: /**
11: * 在页面的显示类型,如是时间类型,纯数字12: */13: FieldViewType type() default FieldViewType.STRING;
14:15: /**
16: * 字段对应的LABEL,将存在于resource.properties17: */18: String label() default "";
19:20: /**
21: * 模块名,比如它属于基础模块foundation,它包里展现为子包22: */23: String subpackage() default "";
24:25: /**
26: * 是否在页面显示,默认显示27: */28: boolean needDisplay() default true;29:30: /**
31: * 是否在页面要排序,默认不排32: */33: boolean sortable() default false;34:35: int width() default 150;36: }37:
1: public enum FieldViewType {2: STRING("默认的字符串"),3: DATE("日期"),4: DATETIME("日期时间"),
5: INT("整形"),
6: COMBOX("下拉列表"),
7: DOUBLE("浮点"),8: ;9: private String label;10:11: public String getLabel() {12: return label;
13: }14:15: private FieldViewType(String label) {16: this.label = label;17: }18: }
2.Annotation的使用
这有几个好处,知道哪个属性的中文名称是什么,各种属性是什么,相当于一种注解的作用。工作量并不大,但却蛮有用。
1: @Entity2: @FieldView(subpackage = "quartz", label = "定时任务")3: public class JobEntity extends IdEntity {4: @NotNull5: @Column(unique = true)
6: @FieldView(label = "任务名",sortable=true)7: private String jobName;//任务名8: @NotNull@FieldView(label = "类/BEAN名")
9: private String jobClass;//类名或者bean名10: @FieldView(label = "方法名")private String jobMethod;//如果为bean名,对应执行的方法11: @NotNull@FieldView(label = "表达式")private String jobCronExpress;//表达式12: @NotNull@FieldView(label = "任务描述")private String jobDesc;//任务描述13: @FieldView(label = "组名")private String jobGroupName;//Group名14: @ElementCollection(fetch = FetchType.LAZY)15: @CollectionTable(name = "t_job_properties")
16: private Map<String, String> properties = new HashMap<String, String>();17: @FieldView(label = "执行次数")private int jobExecCount;18: @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
19: @Temporal(TemporalType.TIMESTAMP)20: @FieldView(label = "创建时间")private Date createTime = new Date();21: @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
22: @Temporal(TemporalType.TIMESTAMP)23: @FieldView(label = "最后执行时间")private Date lastExecTime;24: @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
25: @Temporal(TemporalType.TIMESTAMP)26: @FieldView(label = "下次执行时间")private Date nextExecTime;27: // true=继承Job类,false=spring bean,没有继承job类
28: @FieldView(label = "类是否BEWAN")private boolean jobClassIsBeanName = false;29: @Enumerated(EnumType.STRING)30: @FieldView(label = "状态")private JobStatus status = JobStatus.WAITTING;31:32: @FieldView(label = "本次执行时长(MS)")private long jobUsedTime;//ms
期间,也可以加入一些java validator,或者hibernate validator的校验,在代码解析时,会根据这里的设置,而设置不同的UI检验,如长度只能是100,那么UI会控制。
如
1: @FieldView(label = "姓名", type = FieldViewType.STRING, sortable = true)2: @NotNull3: private String name;4:5: @FieldView(label="年龄")
6: private Integer age;7:8: @Size(min = 2, max = 12)9: @Column(name = "f_desc", length = 12)
10: @FieldView(needDisplay = true)
11: private String desc;
1: @FieldView(label = "宽(厘米)", width = 50, type = FieldViewType.INT)
2: @Column(name = "f_width")
3: private int width;// 单位 cm4:5: @FieldView(label = "长(厘米)", width = 50, type = FieldViewType.INT)
6: @Column(name = "f_height")
7: private int height;
3.代码解析
1: /**
2: * 每个field的对应翻译后的bean3: *4: * @author xiayong5: *6: */7: public class FieldViewBean {8: private String name;
9: //@SuppressWarnings("unused")
10: //private Field field;
11: private Class<?> type;
12: private FieldViewType viewType;
13: private String label;
14: boolean needDisplay;
15:16: boolean require;
17: boolean sortable;
18: private int min;19: private int max;20:21: private int width;22:23: public FieldViewBean(Field field) {
24: //this.field = field;
25: type = field.getType();26: name = field.getName();27: processFieldViewAnnotation(field);28: processFieldValidatorAnnotation(field);29: }30:31: private void processFieldValidatorAnnotation(Field field) {32: NotBlank notBlank = field.getAnnotation(NotBlank.class);
33: NotEmpty notEmpty = field.getAnnotation(NotEmpty.class);
34: if (notBlank != null || notEmpty != null) {35: needDisplay = true;
36: require = true;
37: }38:39: Size size = field.getAnnotation(Size.class);
40: if (size != null) {41: min = size.min();42: max = size.max();43: }44: //TODO,这可以增加更多对标记的支持
45: Column column = field.getAnnotation(Column.class);
46: if (column != null) {47: if (max == 0) {
48: max = column.length()/2;49: }50: if (column.nullable() == false) {51: require = true;
52: }53: }54:55: if(min>0) {
56: require = true;
57: }58: }59:60: private void processFieldViewAnnotation(Field field) {61: FieldView annotation = field.getAnnotation(FieldView.class);
62: viewType = annotation.type();63: label = annotation.label();64: needDisplay = annotation.needDisplay();65: width=annotation.width();66: sortable=annotation.sortable();67:68: if (StringUtils.isEmpty(label)) {
69: label = name;70: }71: }72:73: public String getName() {
74: return name;
75: }76:77: public Class<?> getType() {
78: return type;
79: }80:81: public FieldViewType getViewType() {
82: return viewType;
83: }84:85: public String getLabel() {
86: return label;
87: }88:89: public boolean isNeedDisplay() {90: return needDisplay;
91: }92:93: public int getMin() {94: return min;
95: }96:97: public int getMax() {98: return max;
99: }100:101: @Override102: public String toString() {
103: return "FieldViewBean [fieldName=" + name + ", type=" + type + ", viewType=" + viewType + ", label=" + label104: + ", needDisplay=" + needDisplay + ", min=" + min + ", max=" + max + "]";105: }106:107: public boolean isRequire() {108: return require;
109: }110:111: public boolean isSortable() {112: return sortable;
113: }114:115: public int getWidth() {116: return width;
117: }118: }119:
1: public class FieldViewAnnotationHelper {2: private final Map<Class<?>, List<FieldViewBean>> infocache = new ConcurrentHashMap<Class<?>, List<FieldViewBean>>();3:4: public EntityCodeGenBean getBean(Class<?> clazz) {5: EntityCodeGenBean bean = new EntityCodeGenBean();
6: bean.setViewFields(getViewFields(clazz));7: bean.setClassName(clazz.getSimpleName());8: bean.setChineseName(clazz.getSimpleName());9: bean.setFullClassName(clazz.getCanonicalName());10:11: FieldView annotation = clazz.getAnnotation(FieldView.class);
12: if (null != annotation) {
13: if (StringUtils.isNotBlank(annotation.label())) {
14: bean.setChineseName(annotation.label());15: }16: if (StringUtils.isNotBlank(annotation.subpackage())) {
17: bean.setSubpackage(annotation.subpackage());18: }19: }20:21: return bean;
22: }23:24: private List<FieldViewBean> getViewFields(Class<?> clazz) {25: List<FieldViewBean> fields = infocache.get(clazz);
26: if (fields == null) {
27: synchronized (infocache) {28: infocache.put(clazz, fields = collectInfo(clazz));29: }30: }31: return fields;
32: }33:34: private List<FieldViewBean> collectInfo(Class<?> clazz) {35: List<FieldViewBean> fields = new ArrayList<FieldViewBean>();
36: Class<?> superClazz = clazz;
37: ArrayList<Field> members = new ArrayList<Field>();
38: while (superClazz != null && superClazz != Object.class) {39: members.addAll(Arrays.asList(superClazz.getDeclaredFields()));40: superClazz = superClazz.getSuperclass();41: }42: for (Field member : members) {
43: if (member.isAnnotationPresent(FieldView.class)) {44: member.setAccessible(true);
45: fields.add(new FieldViewBean(member));
46: }47: }48: return fields;
49: }50:51: }
4.引擎,其实就是把解析后的类发给Velocity模板,再给模板去执行罢了
1: public interface CodeGenerator {2:3: void process(Class<?> clazz,String... templateNames);4: }5: public class CodeGeneratorImpl implements CodeGenerator {6: private static final Log logger = LogFactory.getLog(CodeGeneratorImpl.class);7: private TemplateEngine templateEngine;
8: private FieldViewAnnotationHelper helper;
9:10: public CodeGeneratorImpl() {
11: templateEngine = new TemplateEngineImpl();
12: helper = new FieldViewAnnotationHelper();
13: }14:15: @Override16: public void process(Class<?> clazz, String... templateNames) {17: EntityCodeGenBean bean=helper.getBean(clazz);18: if(bean.getViewFields()==null ||bean.getViewFields().isEmpty()) {
19: logger.warn("实体没有属性定义了@FieldView标记,不能自动生成代码.");
20: return;
21: }22:23: Map<String, Object> context = new HashMap<String, Object>();24: context.put("bean", bean);
25: context.put("stringUtils", StringUtils.class);26: context.put("util", Util.class);27:28: String subpackage=bean.getSubpackage();
29: makeSubpackageDir(subpackage);30:31: for (String templateName : templateNames) {32: String outputName =subpackage+File.separatorChar+ clazz.getSimpleName() + StringUtils.substringBefore(templateName, ".vm");33: logger.debug(String.format("正在生成代码:Class:%s,TemplateName:%s,OutputName:%s" , clazz.getSimpleName(), templateName,34: outputName));35: templateEngine.acceptTemplate(templateName).mergeIntoFile(context, outputName);36: logger.debug("successful generated!");
37: }38: }39:40: private void makeSubpackageDir(String subpackage) {41: if(StringUtils.isNotEmpty(subpackage)) {
42: File sub=new File(subpackage);43: if(!sub.exists()) {44: sub.mkdir();
45: }46: }47: }48:49: }
5.模板
Auto.properties.vm
1: #foreach($field in $bean.viewFields)
2: ${bean.className}.$field.name=$util.toUnicode($field.label)3: #end
index.jsp.vm
1: <%@ page contentType="text/html;charset=UTF-8" %>
2: <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>3: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>4: <html xmlns="http://www.w3.org/1999/xhtml">
5: <head>6: <title>${bean.chineseName}</title>7: <script type="text/javascript">
8:9: serverUrl = "$stringUtils.uncapitalize($bean.className)";
10:11: function formatOperation(value,row){
12: return '<a id="editButton" href="#" class="easyui-linkbutton" iconCls="icon-edit" disabled="true" plain="true" onclick="editUser();return false;">编辑</a>';13: }14:15: </script>16: </head>17:18: <body>19: <!-- start add -->20: <div id="addDialog" icon="icon-save" class="easyui-dialog" title="添加记录" style="width:430px;height:400px;"21: buttons="#dlg-buttons" resizable="true" closed="true">22: <br/>23: <br/>24:25: <form:form modelAttribute="addForm" action="$stringUtils.uncapitalize($bean.className)" method="post">26: <input type="hidden" name="id" id="id"/>27: <table id="addTable" align="center" cellpadding="0" cellspacing="7">28: #foreach($field in $bean.viewFields)
29: #if($field.needDisplay)
30: <tr>31: <td class="addtd"><label for="$field.name">$field.label #if($field.require) <font color="red">*</font> #end </label></td>32: <td class="addtd">33: <input type="text" name="$field.name" id="$field.name" value="" #if($field.require) required="true" #end34: #if($field.max>0) validtype="length[${field.min},${field.max}]" #end35: #if($field.viewType=='DATE') class="easyui-datebox" #else class="easyui-validatebox text" #end36: />37: </td>38: </tr>39: #end
40: #end
41: </table>42: </form:form>43: </div>44:45: <div id="dlg-buttons">
46: <a href="#" class="easyui-linkbutton" onclick="javascript:$('#addForm').submit();">保存</a>47: <a href="#" class="easyui-linkbutton" onclick="javascript:$('#addDialog').dialog('close')">关闭</a>48: </div>49:50: <!-- end add -->
51:52: <div class="demo-info" style="margin-bottom:10px">53:54: <div class="demo-tip icon-tip"></div>55:56: <div >${bean.chineseName}管理.</div>57:58: </div>59:60:61:62: <table id=dg class="easyui-datagrid" style="width:950px;height:400px"63: url="$stringUtils.uncapitalize($bean.className)/grid" idField="id"64: title="${bean.chineseName}查看" iconCls="icon-save" toolbar="#toolbar"65: singleSelect="true"
66: rownumbers="true" pagination="true">67: <thead>68: <tr>69: #foreach($field in $bean.viewFields)
70: #if($field.needDisplay)
71: <th field="$field.name" width="${field.width}" #if($field.sortable) sortable="true" #end>$field.label</th>72: #end
73: #end
74: </tr>75: </thead>76: </table>77: <div id="tb" style="padding:5px;height:auto">78:79: <div id="toolbar">
80:81: <a href="#" class="easyui-linkbutton" iconCls="icon-add" plain="true" onclick="javascript:addUser();return false;">新增</a>82:83: <a id="editButton" href="#" class="easyui-linkbutton" iconCls="icon-edit" disabled="true" plain="true" onclick="editUser();return false;">编辑</a>84: <a id="deleteButton" href="#" class="easyui-linkbutton" iconCls="icon-remove" disabled="true" plain="true" onclick="deleteSelected();return false;">删除所选</a>85:86: <input id="ss" class="easyui-searchbox"87: searcher="qq"
88: prompt="请输入查询条件,按回车开始查询" menu="#mm" style="width:300px"></input>89: <div id="mm" style="width:120px">90: #foreach($field in $bean.viewFields)
91: #if($field.needDisplay)
92: <div name="$field.name" iconCls="icon-ok">$field.label</div>93: #end
94: #end
95: </div>96: </div>97:98: </div>99:100: </body>101: </html>102:
。。。
6.使用
1: CodeGenerator generator = new CodeGeneratorImpl();
2:3: generator.process(DemoEntity.class, "index.jsp.vm","Controller.java.vm","Auto.properties.vm");
结果:
1: <%@ page contentType="text/html;charset=UTF-8" %>
2: <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>3: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>4: <html xmlns="http://www.w3.org/1999/xhtml">
5: <head>6: <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />7: <title>DEMO示例</title>8: <script type="text/javascript">
9:10: serverUrl = "demoEntity";
11:12: function formatOperation(value,row){
13: return '<a id="editButton" href="#" class="easyui-linkbutton" iconCls="icon-edit" disabled="true" plain="true" onclick="editUser();return false;">编辑</a>';14: }15:16: </script>17: </head>18:19: <body>20: <!-- start add -->21: <div id="addDialog" icon="icon-save" class="easyui-dialog" title="添加记录" style="width:430px;height:400px;"22: buttons="#dlg-buttons" resizable="true" closed="true">23: <br/>24: <br/>25:26: <form:form modelAttribute="addForm" action="demoEntity" method="post">27: <input type="hidden" name="id" id="id"/>28: <table id="addTable" align="center" cellpadding="0" cellspacing="7">29: <tr>30: <td><label for="name">姓名</label></td>31: <td>32: <input type="text" name="name" id="name" value="" required="true" class="easyui-validatebox text" />33: </td>34: </tr>35: <tr>36: <td><label for="age">年龄</label></td>37: <td>38: <input type="text" name="age" id="age" value="" class="easyui-validatebox text" />39: </td>40: </tr>41: <tr>42: <td><label for="desc">desc</label></td>43: <td>44: <input type="text" name="desc" id="desc" value="" validtype="length[2,12]" class="easyui-validatebox text" />45: </td>46: </tr>47: <tr>48: <td><label for="createDate">createDate</label></td>49: <td>50: <input type="text" name="createDate" id="createDate" value="" class="easyui-datebox" />51: </td>52: </tr>53: </table>54: </form:form>55: </div>56:57: <div id="dlg-buttons">
58: <a href="#" class="easyui-linkbutton" onclick="javascript:$('#addForm').submit();">保存</a>59: <a href="#" class="easyui-linkbutton" onclick="javascript:$('#addDialog').dialog('close')">关闭</a>60: </div>61:62: <!-- end add -->
63:64: <div class="demo-info" style="margin-bottom:10px">65:66: <div class="demo-tip icon-tip"></div>67:68: <div >DEMO示例管理.</div>69:70: </div>71:72:73:74: <table id=dg class="easyui-datagrid" style="width:950px;height:400px"75: url="demoEntity/grid" idField="id"76: title="DEMO示例查看" iconCls="icon-save" toolbar="#toolbar"77: singleSelect="true"
78: rownumbers="true" pagination="true">79: <thead>80: <tr>81: <th field="name" width="220" >姓名</th>82: <th field="age" width="220" >年龄</th>83: <th field="desc" width="220" >desc</th>84: <th field="createDate" width="220" >createDate</th>85: </tr>86: </thead>87: </table>88: <div id="tb" style="padding:5px;height:auto">89:90: <div id="toolbar">
91:92: <a href="#" class="easyui-linkbutton" iconCls="icon-add" plain="true" onclick="javascript:addUser();return false;">新增</a>93:94: <a id="editButton" href="#" class="easyui-linkbutton" iconCls="icon-edit" disabled="true" plain="true" onclick="editUser();return false;">编辑</a>95: <a id="deleteButton" href="#" class="easyui-linkbutton" iconCls="icon-remove" disabled="true" plain="true" onclick="deleteSelected();return false;">删除所选</a>96:97: <input id="ss" class="easyui-searchbox"98: searcher="qq"
99: prompt="Please Input Value" menu="#mm" style="width:300px"></input>100: <div id="mm" style="width:120px">101: <div name="name" iconCls="icon-ok">姓名</div>102: <div name="age" iconCls="icon-ok">年龄</div>103: <div name="desc" iconCls="icon-ok">desc</div>104: <div name="createDate" iconCls="icon-ok">createDate</div>105: </div>106: </div>107:108: </div>109:110: </body>111: </html>112:
一个包括CRUD基本功能的页面搞定,只要微调一下UI,以及增加相关的个性的业务代码即可。
今后要有变化,只要调整entity.java类即可,模型的变化,自动生成了相关的其它代码,是不是很爽啊。
之前我还做了一个生成器,基于Eclipse插件的,点hbml.xml文件右键,会有相关的菜单项,进入界面后,再进行相关配置,就可以直接生成代码,不过那个代码是基于hiberante.hbm.xml的,这个是基于entity.java的扩展的。
当然这个可以扩展成这样,
未完善部分还包括,对各种类型的处理。