近两年项目回顾系列——velocity模板引擎

提起模板引擎,大多数做J2EE的人第一反应是freemarker。毕竟freemarker出的时间早,使用的人也较多,常在互联网项目中使用。freemarker比起jsp来说不用编译,速度更快。但我们项目组的View层主要是Flex富客户端搞定,有模板引擎需求的时候选择了相对简单和轻量的velocity。下面回顾一下自己用velocity模板引擎写了一个代码生成器。
业务背景:
  系统中共有千余张表。主要的申请信息、注册信息、流程信息约有400余张表。由于用户人为操作原因、录入/校对失误、系统BUG等难免会有错误数据出现。对错误数据的纠正之前总局内长期依赖于计算机处管理员用TOAD/PL SQL等工具去库里改,这种方式比较危险,且操作非常不便。所以新系统中需要一个可视化、全表信息均可修改的数据维护功能模块。
主要问题:
  维护的表非常非常多,上面提到至少也需要维护数百张表。开发这样的模块若为没一张表开发页面则工作量非常巨大。和架构师商量,我们大体有两种思路:1、每一张表的页面动态生成,每次选择所需维护的表时,查该表所有字段,一一罗列出来即可。2、使用模板引擎,为每一张表生成一个相对合理布局的维护页面。经过讨论我们选择了后者,主要是前者动态生成的页面可能非常丑,样式、布局、拆分合并等操作在Flex的as中控制起来比较麻烦。
最终实现:
  使用velocity模板引擎为对应的数百张表生成信息维护页面。代码包括展现部分(input、radio、checkbook、textarea、datagrid等元素组成)、事件注册、callback、对外接口等。废话不说,上代码:

核心方法—根据模板和参数使用VelocityEngine获取字符串:

  /**
     * 根据模板取得传入参数后的字符串
     * 
     * @param tmpPath
     *            模板路径
     * @param tmpFile
     *            模板名称,如果为空则抛出异常
     * @param encoding
     *            取模板的字符集,如果为空则按照默认的utf-8
     * @param names
     *            所有变量名称集合
     * @param values
     *            所有变令对应的值
     * @return 返回生成的对应的视图的字符串
     * @throws Exception
     */
    public static synchronized String getStringByTemplateFile(String tmpPath,String tmpFile, String encoding, String[] names, Object[] values)throws Exception {
        if (tmpFile == null || tmpFile.equals(""))
            throw new Exception("取得模板文件名称为空,无法生成对应的");
        // 默认字符集为gbk
        if (encoding == null || encoding.equals(""))
            encoding = "gbk";
        int len = 0;// 设置初始化变量个数
        // 通过计算取names和values中个数少的为长度,有一个为null,则就不向里面设置变量,则得到就是视图模板
        if (names != null && values != null)
            len = names.length < values.length ? names.length : values.length;
        // 初始化环境变量
        Properties p = new Properties();
        p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, tmpPath);// 存放模板的地址
        VelocityEngine ve = new VelocityEngine();
        ve.init(p);
        // 根据模板生成对应的对象
        Template template = ve.getTemplate(tmpFile, encoding);
        VelocityContext context = new VelocityContext();
        for (int i = 0; i < len; i++)
            context.put(names[i], values[i]);
        StringWriter writer = new StringWriter();
        template.merge(context, writer);
        return writer.toString();
    }

然后用IO将此文件写到名为“维护类型_维护表对应POJO类名.mxml”中。


下面是一个velocity模板的代码(makeAppFormMXMLGroup.vm):
其中有一些简单的循环、分支。

<?xml version="1.0" encoding="utf-8"?>
<dc:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
         xmlns:s="library://ns.adobe.com/flex/spark" 
         xmlns:mx="library://ns.adobe.com/flex/mx"
         xmlns:dc="http://digitalchina.dc.tm"
         name="数据维护-申请信息维护-$!{tab.tabComments}维护">
         
    <!-- 创建人:liujian       创建时间:$!{info.createTime} *************** -->
    
    <!-- 页面布局    ******************************************** -->
    <dc:layout>
        <s:BasicLayout/>
    </dc:layout>
    
    <!-- metada       ****************元数据********************** -->
    
    
    <!-- script       ******************AS脚本********************** -->    
    <fx:Script>
        <![CDATA[
             
            import $!{info.modePackageAs}.$!{tab.clsName};
            import com.dc.business.tmaas.sjwh.components.SjwhUtils;

            [Bindable]
            public var data_$!{tab.clsName}:$!{tab.clsName} = null;            //$!{tab.tabComments}对象,以"data_"+申请书对象命名
            ##计算每一行的高度
            #set($rowNum = $!{tcList.size()}/2)
            #if($!{tcList.size()}%2 != 0)
                #set($rowNum = $rowNum + 1)
            #end
            
            #set ($heightRate = 100/$rowNum)
            
            /**
             * 重置本页数据
             * */
            public function reset():void{
                data_$!{tab.clsName} = new $!{tab.clsName}();
                //重置表单样式,必须
                SjwhUtils.resetTableStyle(mainTable);
                //页面有radio、dropdownList时,重置本页的radio、dropdownList
            }
            
            /**
             * 刷新本页数据方法,包含初始化下拉菜单等操作 
             * */
            public function refresh():void{
                //有dropdownList的场合,初始化dropdownList。
//                if(data_TTmaasAppXXXXAppform.xxx == xxx){
//                    someDropDownList.selectedIndex = xxx;
//                }
            }

            /**
             * 申请信息变化监听
             * */
            protected function appInfoChangedHandler(labelName:String, event:Event):void{
                if(data_$!{tab.clsName} != null){
                    parentDocument.conValueChangeHandler(event.currentTarget, data_$!{tab.clsName}, labelName);
                }
            }
        ]]>
    </fx:Script>
    
    <!-- declarations ********************声明非可视化组件************ -->
    
    <fx:Declarations>
        <!-- 将非可视元素(例如服务、值对象)放在此处 -->
        <mx:DateFormatter id="df" formatString="YYYY-MM-DD"/>
    </fx:Declarations>
    
    <!-- UICpmponents ****************可视化组件********************* -->
    <dc:Table width="100%" height="100%" id="mainTable">
        <dc:Tr width="100%" height="$heightRate%">
##设置一个变量,用于记录是奇数,偶数。
#set ($i=0)
#foreach( $column in $tcList)
#set($i=$i+1)
            <dc:Td horizontalAlign="right" verticalAlign="middle" styleName="tdColor" width="20%">
                <dc:Label  text="$!{column.colComments}" styleName="headerFont"/>
            </dc:Td>
            <dc:Td horizontalAlign="left" verticalAlign="middle" #if($column.dataType != 'DATE')paddingLeft="10"#end width="40%" #if($i == $!{tcList.size()} && $!{tcList.size()}%2 != 0)colSpan="3"#end>
#if($column.dataType == 'DATE')
                <dc:DateField formatString="YYYY-MM-DD" text="{df.format(data_$!{tab.clsName}.$!{column.fieldName})}" name="$!{column.fieldName}" width="100%" height="100%" change="appInfoChangedHandler('$!{column.colComments}',event)"/>
#else
                <dc:TextArea name="$!{column.fieldName}" text="{data_$!{tab.clsName}.$!{column.fieldName}}" #if($!{column.fieldName}== 'id' || $!{column.fieldName}== 'barCode' || $!{column.fieldName}== 'appNum') editable="false"  #end borderVisible="false" width="99%" height="99%" focusOut="appInfoChangedHandler('$!{column.colComments}',event)"/>
#end
            </dc:Td>
##偶数行/末尾行,关闭TR。
#if($i % 2 == 0 || $i == $!{tcList.size()})
        </dc:Tr>
#end
##偶数且非末尾行,开启新TR
#if($i%2 == 0 && $i != $!{tcList.size()})
        <dc:Tr width="100%" height="$heightRate%">
#end
#end
    </dc:Table>
</dc:Group>

下面是生成一些文件的结果:

然后使用一个NavigatorContent放到ViewStack中,creationPolicy="auto"自动创建。(这样在选择一个表的时候才创建页面对象,否则设置成all,数百个页面创建会内纯溢出。)


点击时,创建维护对象:


后来,经过引申,开发了一个功能更强大的代码生成器:
使用velocity模板生成hibernate的POJO、mapping文件,前台actionscript的model等等:

之后,当库结构发生改变时,用此页面重新生成POJO和mapping文件,打包即可。

posted @ 2013-05-09 13:51  大树的博客  Views(1034)  Comments(0Edit  收藏  举报