也谈代码生成器--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属性,所以可以不重复定义,这里指客户端校验,如果要服务端校验,那么加上@NotNull
  4:  * @author xiayong
  5:  * 
  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.properties
 17:    */
 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: @Entity
  2: @FieldView(subpackage = "quartz", label = "定时任务")
  3: public class JobEntity extends IdEntity {
  4:   @NotNull
  5:   @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:   @NotNull
  3:   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;// 单位 cm
  4: 
  5:   @FieldView(label = "长(厘米)", width = 50, type = FieldViewType.INT)
  6:   @Column(name = "f_height")
  7:   private int height;

3.代码解析

  1: /**
  2:  * 每个field的对应翻译后的bean
  3:  * 
  4:  * @author xiayong
  5:  * 
  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:   @Override
102:   public String toString() {
103:     return "FieldViewBean [fieldName=" + name + ", type=" + type + ", viewType=" + viewType + ", label=" + label
104:         + ", 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:   @Override
 16:   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" #end 
 34:          #if($field.max>0) validtype="length[${field.min},${field.max}]" #end  
 35:          #if($field.viewType=='DATE') class="easyui-datebox" #else class="easyui-validatebox text" #end  
 36:          />
 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: 
image

一个包括CRUD基本功能的页面搞定,只要微调一下UI,以及增加相关的个性的业务代码即可。

今后要有变化,只要调整entity.java类即可,模型的变化,自动生成了相关的其它代码,是不是很爽啊。

 

之前我还做了一个生成器,基于Eclipse插件的,点hbml.xml文件右键,会有相关的菜单项,进入界面后,再进行相关配置,就可以直接生成代码,不过那个代码是基于hiberante.hbm.xml的,这个是基于entity.java的扩展的。

当然这个可以扩展成这样,

未完善部分还包括,对各种类型的处理。

posted on 2012-02-29 15:02  夏雨的天空  阅读(786)  评论(0编辑  收藏  举报

导航