在velocity中自定義標簽

t4bhk64rs 8年前發布 | 49K 次閱讀 Webx Velocity 模板引擎

來自: http://wsztrush.github.io/編程技術/2016/01/21/VELOCITY-DIRECTIVE.html

用velocity搞html頁面很好用,通過一些標簽:

  1. #if
  2. #set
  3. #foreach

幾乎能實現所有需要的渲染邏輯。但是,如果能自定義一些標簽,就可以更靈活地在vm中玩耍了,尤其是在實現工具的時候。

自定義標簽

標簽的定義是用velocity的屬性來控制的,默認標簽的定義存放在:

org/apache/velocity/runtime/defaults/directive.properties

其中內容為(分別對應 #foreach 等標簽、屬性值為處理類名):

directive.1=org.apache.velocity.runtime.directive.Foreach
directive.2=org.apache.velocity.runtime.directive.Include
directive.3=org.apache.velocity.runtime.directive.Parse
directive.4=org.apache.velocity.runtime.directive.Macro
directive.5=org.apache.velocity.runtime.directive.Literal
directive.6=org.apache.velocity.runtime.directive.Evaluate
directive.7=org.apache.velocity.runtime.directive.Break
directive.8=org.apache.velocity.runtime.directive.Define

標簽(自定義 or 系統的)對應的處理類都需要繼承:

org.apache.velocity.runtime.directive.Directive

并實現三個方法:

方法 作用
getName 返回標簽名
getType 類型,分為LINE和BLOCK兩種
render 渲染方法,所有的實現邏輯在這里實現

類型為 LINE 的標簽在使用時不需要 #end 來標記結束,而且標簽的內容可以分在多行:

#test(123 \n2)

類型為 BLOCK 則需要用#end結尾:

#test(123) abc #end

要想讓自己定義的標簽生效,需要在velocity初始化時設置屬性:

  • 屬性名: userdirective (在velocity中寫死的)
  • 屬性值:你實現的Directive的類全路徑

現在我們來看個最簡單的例子:

public static class Test extends Directive {
    public String getName() {
        return "test";
    }
    public int getType() {
        return LINE;
    }
    public boolean render(InternalContextAdapter context, 
        Writer writer, 
        Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
        writer.write("abc");// 啥都不做,直接輸出abc
        return true;
    }
}

然后初始化一個VelocityEngine測試 #test 標簽:

VelocityEngine engine = new VelocityEngine();
//.. 省略若干屬性設置
engine.addProperty("userdirective", "Test");

Template template = engine.getTemplate("#test"); StringWriter writer = new StringWriter(); template.merge(new EasydtContext(), writer); System.out.println(writer.toString()); // 輸出:abc</pre>

用標簽來實現的功能無非是做一些字符串的處理,處理過程中能拿到的信息都在render方法參數中:

參數 含義
context 保存上下文
writer 用來輸出字符串
node 抽象語法樹中和當前位置對應的節點

可以從節點(node)中拿到一些有意思的信息,比如:

  • 模板名稱
  • 行號
  • 列號
  • 子節點

有了這些信息類似 這里 實現#cache時就不需要手動傳入key了。回到正題,render其實是在抽象語法樹上遞歸下降的過程,比如#foreach中:

// render方法中
node.jjtGetChild(3).render(context, writer);

當然我們也可以繼續用上面的例子測試,修改render方法:

render(/* ... */){
    StringWriter tmpWriter = new StringWriter();
    node.jjtGetChild(0).render(context, tmpWriter); // 遞歸執行
    writer.write(tmpWriter.toString());
    return true;
}

測試使用的模板為 #test()#if(true)abc#end#end ,輸出結果依然是abc。

在webx中擴展

在springmvc中設置velocity的屬性還是非常簡單的(略),但是webx做了相當多的約定性質的擴展( 參考 ),下面來看在webx中自定義velocity標簽以及其他擴展的方法,創建文件:

/META-INF/services-template-engines-velocity-plugins.bean-definition-parsers

其中的內容為:

my-support=com.xxx.MySupportDefinitionParser

其中:

  • 屬性:標簽名稱
  • 值:解析實現類,用來解析配置

編輯 my-support.xsd ,格式可以參考這里,這里就不寫了,然后需要實現:

com.xxx.MySupport

在其初始化方法(init)中可以對velocity的屬性進行設置,具體的實現邏輯可以參考EscapeSupport,最后將添加到webx的配置中:

<services:template xmlns="http://www.alibaba.com/schema/services/template/engines" searchExtensions="true">
    <velocity-engine>
        <plugins>
            <vm-plugins:my-support/>
        </plugins>
    </velocity-engine>
</services:template>

思考和總結

自定義標簽的功能像是在velocity中開了一個口子,讓我們實現自己的邏輯,甚至可以在vm中嵌套使用渲染引擎(在上面的例子中可以看出來這點吧):

  1. 使用velocity渲染
  2. 使用自定義的引擎渲染,將最終的結果寫到writer中

如果將這種看作是在velocity處理后擴展,那么:

有沒有辦法在velocity之前進行擴展?

答案是肯定的,現在想到的比較簡單的方式是在ResourceLoader上做手腳,應該還有其他的思路。

</div>

 本文由用戶 t4bhk64rs 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!