Beetl2.7.16中文文檔(2)之高級功能

3. 高級功能

3.1. 配置GroupTemplate

Beetl建議通過配置文件配置GroupTemplate,主要考慮到IDE插件未來可能會支持Beetl模板,模板的屬性,和函數(shù)等如果能通過配置文件獲取,將有助于IDE插件識別。 配置GroupTemplate有倆種方法

  • 配置文件: 默認(rèn)配置在/org/beetl/core/beetl-default.properties 里,Beetl首先加載此配置文件,然后再加載classpath里的beetl.properties,并用后者覆蓋前者。配置文件通過Configuration類加載,因此加載完成后,也可以通過此類API來修改配置信息
  • 通過調(diào)用GroupTemplate提供的方法來注冊函數(shù),格式化函數(shù),標(biāo)簽函數(shù)等

配置文件分為三部分,第一部分是基本配置,在第一節(jié)講到過。第二部分是資源類配置,可以在指定資源加載類,以及資源加載器的屬性,如下

RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#資源配置,resource后的屬性只限于特定ResourceLoader
#classpath 根路徑
RESOURCE.root= /
#是否檢測文件變化
RESOURCE.autouCheck= true

第1行指定了模板加載器類,在beetl與其他框架集成的時候,模板加載器不一定根據(jù)這個配置,比如spring,它的RESOURCE_LOADER以spring的配置為準(zhǔn)

第4行指定了模板根目錄的路徑,此處/ 表示位于classpath 根路徑下

第6行是否自動檢測模板變化,默認(rèn)為true,開發(fā)環(huán)境下自動檢測模板是否更改。關(guān)于如何自定義ResouceLoader,請參考下一章

配置文件第三部分是擴展部分,如方法,格式化函數(shù)等

#####  擴展 ##############
## 內(nèi)置的方法
FN.date = org.beetl.ext.fn.DateFunction
FN.nvl = org.beetl.ext.fn.NVLFunction
.................
##內(nèi)置的功能包
FNP.strutil = org.beetl.ext.fn.StringUtil

##內(nèi)置的格式化函數(shù)
FT.dateFormat =  org.beetl.ext.format.DateFormat
FT.numberFormat =  org.beetl.ext.format.NumberFormat
.................

##內(nèi)置的默認(rèn)格式化函數(shù)
FTC.java.util.Date = org.beetl.ext.format.DateFormat
FTC.java.sql.Date = org.beetl.ext.format.DateFormat

## 標(biāo)簽類
TAG.include= org.beetl.ext.tag.IncludeTag
TAG.includeFileTemplate= org.beetl.ext.tag.IncludeTag
TAG.layout= org.beetl.ext.tag.LayoutTag
TAG.htmltag= org.beetl.ext.tag.HTMLTagSupportWrapper

FN前綴表示Function,F(xiàn)NP前綴表示FunctionPackage,F(xiàn)T表示format函數(shù),F(xiàn)TC表示類的默認(rèn)Format函數(shù),TAG表示標(biāo)簽類。Beetl強烈建議通過配置文件加載擴展。以便隨后IDE插件能識別這些注冊函數(shù)

3.2. 自定義方法

3.2.1. 實現(xiàn)Function
public class Print implements Function{
        public String call(Object[] paras, Context ctx){
                Object o = paras[0];
                if (o != null){
                        try{
                                ctx.byteWriter.write(o.toString());
                        }catch (IOException e){
                                throw new RuntimeException(e);
                        }
                }
                return "";
        }
}

call方法有倆個參數(shù),第一個是數(shù)組,這是由模板傳入的,對應(yīng)著模板的參數(shù),第二個是Context,包含了模板的上下文,主要提供了如下屬性

  • byteWriter 輸出流
  • template 模板本身
  • gt GroupTemplate
  • globalVar 該模板對應(yīng)的全局變量
  • byteOutputMode 模板的輸出模式,是字節(jié)還是字符
  • safeOutput 模板當(dāng)前是否處于安全輸出模式
  • 其他屬性建議不熟悉的開發(fā)人員不要亂動
  1. call方法要求返回一個Object,如果無返回,返回null即可
  2. 為了便于類型判斷,call方法最好返回一個具體的類,如date函數(shù)返回的就是java.util.Date
  3. call方法里的任何異常應(yīng)該拋出成Runtime異常
3.2.2. 使用普通的java類

盡管實現(xiàn)Function對于模板引擎來說,是效率最高的方式,但考慮到很多系統(tǒng)只有util類,這些類里的方法仍然可以注冊為模板函數(shù)。其規(guī)則很簡單,就是該類的所有public方法。如果還需要Context 變量,則需要在方法最后一個參數(shù)加上Context即可,如

public class util{
        public String print(Object a, Context ctx){
                //balabala...
        }
}

注意

  1. 從beetl效率角度來講,采用普通類效率不如實現(xiàn)Function調(diào)用
  2. 采用的普通java類盡量少同名方法。這樣效率更低。beetl調(diào)用到第一個適合的同名方法。而不像java那樣找到最匹配的
  3. 方法名支持可變數(shù)組作為參數(shù)
  4. 方法名最后一個參數(shù)如果是Context,則beetl會傳入這個參數(shù)。
3.2.3. 使用模板文件作為方法

可以不用寫java代碼,模板文件也能作為一個方法。默認(rèn)情況下,需要將模板文件放到Root的functions目錄下,且擴展名為.html(可以配置文件屬性來修改這倆個默認(rèn)值) 方法參數(shù)分別是para0,para1…..

如下root/functions/page.fn

<%
//para0,para1 由函數(shù)調(diào)用傳入
var current = para0,total = para1,style=para2!'simple'
%>
當(dāng)前頁面 ${current},總共${total}

則在模板中

<%
page(current,total);
%>

允許使用return 表達(dá)式返回一個變量給調(diào)用者,如模板文件functions\now.html

<%
return date();
%>

在任何模板里都可以調(diào)用:

hello time is ${now(),'yyyy-MM-dd'}

也可以在functions建立子目錄,這樣function則具有namespace,其值就是文件夾名

3.3. 自定義格式化函數(shù)

需要實現(xiàn)Format接口

public class DateFormat implements Format{
        public Object format(Object data, String pattern){
                if (data == null)
                        return null;
                if (Date.class.isAssignableFrom(data.getClass())){
                        SimpleDateFormat sdf = null;
                        if (pattern == null){
                                sdf = new SimpleDateFormat();
                        }else{
                                sdf = new SimpleDateFormat(pattern);
                        }
                        return sdf.format((Date) data);
                }else{
                        throw new RuntimeException("Arg Error:Type should be Date");
                }
        }
}

data 參數(shù)表示需要格式化的對象,pattern表示格式化模式,開發(fā)時候需要考慮pattern為null的情況

也可以實現(xiàn)ContextFormat 類抽象方法,從而得到Context,獲取外的格式化信息。

public abstract Object format(Object data,String pattern,Context ctx);

3.4. 自定義標(biāo)簽

標(biāo)簽形式有倆種,一種是標(biāo)簽函數(shù),第二種是html tag。第二種實際上在語法解析的時候會轉(zhuǎn)化成第一種,其實現(xiàn)是HTMLTagSupportWrapper,此類將會尋找root/htmltag目錄下同名的標(biāo)簽文件作為模板來執(zhí)行。類似普通模板一樣,在此就不詳細(xì)說了

3.4.1. 標(biāo)簽函數(shù)

標(biāo)簽函數(shù)類似jsp2.0的實現(xiàn)方式,需要實現(xiàn)Tag類的render方法即可

public class DeleteTag extends Tag{
        @Override
        public void render(){
                // do nothing,just ignore body
                ctx.byteWriter.write("被刪除了,付費可以看")
        }
}

如上一個最簡單的Tag,將忽略tag體,并輸出內(nèi)容

public class XianDeDantengTag extends Tag{
        @Override
        public void render(){
                doBodyRender();
        }
}

此類將調(diào)用父類方法doBodyRender,渲染tag body體

public class CompressTag extends Tag{
        @Override
        public void render(){
                BodyContent  content = getBodyContent();
                String content = content.getBody();
                String zip = compress(conent);
                ctx.byteWriter.write(zip);
        }
}

此類將調(diào)用父類方法getBodyContent ,獲得tag body后壓縮輸出

tag類提供了如下屬性和方法供使用

  • args 傳入標(biāo)簽的參數(shù)
  • gt GroupTemplate
  • ctx Context
  • bw 當(dāng)前的輸出流
  • bs 標(biāo)簽體對應(yīng)的語法樹,不熟悉勿動

3.5. 自定義虛擬屬性

可以為特定類注冊一個虛擬屬性,也可以為一些類注冊虛擬屬性

  • public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 實現(xiàn)VirtualClassAttribute方法可以為特定類注冊一個需要屬性,如下代碼:

      gt.registerVirtualAttributeClass(User.class, new VirtualClassAttribute() {
              @Override
              public String eval(Object o, String attributeName, Context ctx){
                      User user = (User) o;
                      if(attributeName.equals("ageDescritpion")){
                              if (user.getAge() < 10){
                                      return "young";
                              }else{
                                      return "old";
                              }
                      }
              }
      });
    

    User類的所有虛擬屬性將執(zhí)行eval方法,此方法根據(jù)年紀(jì)屬性來輸出對應(yīng)的描述。

  • public void registerVirtualAttributeEval(VirtualAttributeEval e) 為一些類注冊需要屬性,VirtualAttributeEval.isSupport方法將判斷是否應(yīng)用虛擬屬性到此類

    如下是虛擬屬性類的定義

    public interface VirtualClassAttribute{
        public Object eval(Object o, String attributeName, Context ctx);
    }
    public interface VirtualAttributeEval extends VirtualClassAttribute{
        public boolean isSupport(Class c, String attributeName);
    }
    

3.6. 使用額外的資源加載器

某些情況下,模板來源不止一處,GroupTemplate配置了一個默認(rèn)的資源加載器,如果通過gt.getTemplate(key),將調(diào)用默認(rèn)的ResourceLoader,獲取模板內(nèi)容,然后轉(zhuǎn)化為beetl腳本放入到緩存里。你也可以傳入額外的資源管理器加載模板,通過調(diào)用gt.getTemplate(key,otherLoader)來完成;

GroupTemplate gt = new GroupTemplate(conf,fileLoader)
//自定義,參考下一節(jié)
MapResourceLoader dbLoader = new MapResourceLoader(getData());
Template t = gt.getTemplate("db:1", dbLoader);

private Map getData(){
        Map data = new HashMap();
        data.put("db:1", "${a}");
        return data;
}

對于更復(fù)雜的模板資源來源,也可以自定義一個資源加載來完成,參考下一節(jié)

3.7. 自定義資源加載器

如果模板資源來自其他地方,如數(shù)據(jù)庫,或者混合了數(shù)據(jù)庫和物理文件,或者模板是加密的,則需要自定義一個資源加載器。資源加載器需要實現(xiàn)ResourceLoader類。如下:

public interface ResourceLoader{

        /**
         * 根據(jù)key獲取Resource
         *
         * @param key
         * @return
         */
        public Resource getResource(String key);

        /** 檢測模板是否更改,每次渲染模板前,都需要調(diào)用此方法,所以此方法不能占用太多時間,否則會影響渲染功能
         * @param key
         * @return
         */
        public boolean isModified(Resource key);

        /**
         * 關(guān)閉ResouceLoader,通常是GroupTemplate關(guān)閉的時候也關(guān)閉對應(yīng)的ResourceLoader
         */
        public void close();

        /** 一些初始化方法
         * @param gt
         */
        public void init(GroupTemplate gt);

        /**  用于include,layout等根據(jù)相對路徑計算資源實際的位置.
         * @param resource 當(dāng)前資源
         * @param key
         * @return
         */
        public String getResourceId(Resource resource, String key);
}

如下是一個簡單的內(nèi)存ResourceLoader

public class MapResourceLoader implements ResourceLoader{
        Map data;

        public MapResourceLoader(Map data){
                this.data = data;
        }

        @Override
        public Resource getResource(String key){
                String content = (String) data.get(key);
                if (content == null)
                        return null;
                return new StringTemplateResource(content, this);
        }

        @Override
        public boolean isModified(Resource key){
                return false;
        }

        @Override
        public boolean exist(String key){
                return data.contain(key);
        }

        @Override
        public void close(){

        }

        @Override
        public void init(GroupTemplate gt){

        }

        @Override
        public String getResourceId(Resource resource, String id){
                //不需要計算相對路徑
                return id;
        }
}

init方法可以初始化GroupTemplate,比如讀取配置文件的root屬性,autoCheck屬性,字符集屬性,以及加載functions目錄下的所有模板方法 如FileResourceLoader 的 init方法

@Override
public void init(GroupTemplate gt){
        Map<String, String> resourceMap = gt.getConf().getResourceMap();
        if (this.root == null){
                this.root = resourceMap.get("root");
        }
        if (this.charset == null){
                this.charset = resourceMap.get("charset");
        }
        if (this.functionSuffix == null){
                this.functionSuffix = resourceMap.get("functionSuffix");
        }

        this.autoCheck = Boolean.parseBoolean(resourceMap.get("autoCheck"));

        File root = new File(this.root, this.functionRoot);
        this.gt = gt;
        if (root.exists()){
                readFuntionFile(root, "", "/".concat(functionRoot).concat("/"));
        }
}

readFuntionFile 方法將讀取functions下的所有模板,并注冊為方法

protected void readFuntionFile(File funtionRoot, String ns, String path){
        String expected = ".".concat(this.functionSuffix);
        File[] files = funtionRoot.listFiles();
        for (File f : files){
                if (f.isDirectory()){
                        //讀取子目錄
                        readFuntionFile(f, f.getName().concat("."), path.concat(f.getName()).concat("/"));
                } else if (f.getName().endsWith(functionSuffix)){
                        String resourceId = path + f.getName();
                        String fileName = f.getName();
                        fileName = fileName.substring(0, (fileName.length() - functionSuffix.length() - 1));
                        String functionName = ns.concat(fileName);
                        FileFunctionWrapper fun = new FileFunctionWrapper(resourceId);
                        gt.registerFunction(functionName, fun);
                }
        }
}

Resource類需要實現(xiàn)OpenReader方法,以及isModified方法。對于模板內(nèi)容存儲在數(shù)據(jù)庫中,openReader返回一個Clob,isModified 則需要根據(jù)改模板內(nèi)容對應(yīng)的lastUpdate(通常數(shù)據(jù)庫應(yīng)該這么設(shè)計)來判斷模板是否更改

public abstract class Resource{

        /**
         * 打開一個新的Reader
         *
         * @return
         */
        public abstract Reader openReader();

        /**
         * 檢測資源是否改變
         *
         * @return
         */
        public abstract boolean isModified();

參考例子可以參考beetl自帶的ResourceLoader

3.8. 使用CompositeResourceLoader

組合加載器,可以包含多個已有的ResourceLoader,如下代碼將創(chuàng)建一個包含倆個文件和內(nèi)存的ResourceLoader

FileResourceLoader fileLoader1 = new FileResourceLoader(path1);
FileResourceLoader fileLoader2 = new FileResourceLoader(path2);
Map data = getData();
// 根據(jù)id加載
MapResourceLoader mapLoader = new MapResourceLoader(data);

CompositeResourceLoader loader = new CompositeResourceLoader();
loader.addResourceLoader(new StartsWithMatcher("http:").withoutPrefix(), fileLoader2);
loader.addResourceLoader(new StartsWithMatcher("db:"), mapLoader);
loader.addResourceLoader(new AllowAllMatcher(), fileLoader1);

GroupTemplate gt = new GroupTemplate(loader, conf);
Template t = gt.getTemplate("/xxx.html");

如上例子,groupTemplate從CompositeResourceLoader里加載/xxx.html,由于http:和db:前綴都不匹配,因此,將實際采用fileLoader1加載path1+/xxx.html,如下是xxx.html文件內(nèi)容

<%
include("/xxx2.html"){}
include("http:/xxx.html"){}
%>

第2行仍然是由fileLoader1加載,但第3行以http:前綴開頭,因此將fileLoader2加載path2+/xxx.html.xxx.html內(nèi)容如下

<%
include("db:1"){}
%>

因為以db:開頭,因此會采用MapResourceLoader加載,內(nèi)容是key為db:1對模板

3.9. 自定義錯誤處理器

錯誤處理器需要實現(xiàn)ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);

  • beeExceptionos,模板各種異常
  • writer 模板使用的輸出流。系統(tǒng)自帶的并未采用此Writer,而是直接輸出到控制臺

自定義錯誤處理可能是有多個原因,比如

  1. 想將錯誤輸出到頁面而不是控制臺

  2. 錯誤輸出美化一下,而不是自帶的格式

  3. 錯誤輸出的內(nèi)容做調(diào)整,如不輸出錯誤行的模板內(nèi)容,而僅僅是錯誤提示

  4. 錯誤輸出到日志系統(tǒng)里

  5. 不僅僅輸出日志,還拋出異常。默認(rèn)自帶的不會拋出異常,ReThrowConsoleErrorHandler 繼承了ConsoleErrorHandler方法,打印異常后拋出

        public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler{
                @Override
                public void processExcption(BeetlException ex, Writer writer){
                        super.processExcption(ex, writer);
                        throw ex;
                }
        }
    

beetl 提供 ErrorInfo類來wrap BeetlException,轉(zhuǎn)化為較為詳細(xì)的提示信息,他具有如下信息

  • type 一個簡單的中文描述
  • errorCode 內(nèi)部使用的錯誤類型標(biāo)識
  • errorTokenText 錯誤發(fā)生的節(jié)點文本
  • errorTokenLine 錯誤行
  • msg 錯誤消息,有可能沒有,因為有時候errorCode描述的已經(jīng)很清楚了
  • cause 錯誤的root 異常,也可能沒有。

BeetlException 也包含了一個關(guān)鍵信息就是 resourceId,即出錯所在的模板文件

3.10. 自定義安全管理器

所有模板的本地調(diào)用都需要通過安全管理器校驗,默認(rèn)需要實現(xiàn)NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String method) 方法

如下是默認(rèn)管理器的實現(xiàn)方法

public class DefaultNativeSecurityManager implements NativeSecurityManager{

        @Override
        public boolean permit(String resourceId, Class c, Object target, String method){
                if (c.isArray()){
                        //允許調(diào)用,但實際上會在在其后調(diào)用中報錯。不歸此處管理
                        return true;
                }
                String name = c.getSimpleName();
                String pkg = c.getPackage().getName();
                if (pkg.startsWith("java.lang")){
                        if (name.equals("Runtime") || name.equals("Process") || name.equals("ProcessBuilder")
                                        || name.equals("System")){
                                return false;
                        }
                }
                return true;
        }
}

3.11. 注冊全局共享變量

groupTemplate.setSharedVars(Map<String, Object> sharedVars)

3.12. 布局

布局可以通過Beetl提供的include,layout 以及模板變量來完成。模板變量能完成復(fù)雜的布局

  • 采用layout include

      <%
       //content.html內(nèi)容如下:
       layout("/inc/layout.html"){ %>
       this is 正文
       ..........
       <% } %>
    

    如上一個子頁面將使用layout布局頁面,layout 頁面內(nèi)容如下

      <% include("/inc/header.html"){} %>
      this is content:${layoutContent}
      this is footer:
    

    layoutContent 是默認(rèn)變量,也可以改成其他名字,具體請參考layout標(biāo)簽函數(shù)

    全局變量總是能被布局用的頁面所使用,如果布局頁面需要臨時變量,則需要顯示的傳入,如:

      <%
      var user= model.user;
      include("/inc/header.html",{title:'這是一個測試頁面',user:user}){} 
      %>
    

    這樣,title和user成為全局變量,能被header.html 及其子頁面引用到

  • 繼承布局:采用模板變量和include

            <%
                    var jsPart = {
            %>
            web頁面js部分
    
            <% }; %>
    
            <%
                    var htmlPart = {
            %>
            web頁面html部分
    
            <% };
            include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){}
            %>
    
          layout.html頁面如下:
    
            <body>
            <head>
            ${jsSection}
            </head>
            <body>
            .......
            ${htmlSection}
            </body>
    

3.13. 性能優(yōu)化

Beetl性能已經(jīng)很快了,有些策略能更好提高性能

  • 使用二進(jìn)制輸出,此策略可以使模板在語法分析的時候?qū)㈧o態(tài)文本轉(zhuǎn)化為二進(jìn)制,省去了運行時刻編碼時間,這是主要性能提高方式。但需要注意,此時需要提供一個二進(jìn)制輸出流,而不是字符流,否則性能反而下降
  • 使用FastRuntimeEngine,默認(rèn)配置。 此引擎能對語法樹做很多優(yōu)化,從而提高運行性能,如生成字節(jié)碼來訪問屬性而不是傳統(tǒng)的反射訪問。關(guān)于引擎,可能在新的版本推出更好的引擎,請隨時關(guān)注。
  • 通過@type 來申明全局變量類型,這不能提高運行性能,但有助于模板維護(hù)
  • 自定義ResourceLoader的isModified必須盡快返回,因此每次渲染模板的時候都會調(diào)用此方法

為什么Beetl性能這么好…………(待續(xù))

3.14. 分布式緩存模板

Beetl模板引擎模板在同一個虛擬機里緩存Beetl 腳本。也可以將緩存腳本到其他地方,只要實現(xiàn)Cache接口,并設(shè)置ProgramCacheFactory.cache即可,這樣GroupTemplate將從你提供的Cache中存取Beetl腳本

此功能未被很好測試

3.15. 定制輸出

占位符輸出允許定制。如所有日期類型都按照某個格式化輸出,而不需顯式的使用格式化輸出,或者為了防止跨腳本站點攻擊,需要對類型為String的值做檢查等,不必使用格式化函數(shù),可以直接對占位符輸出進(jìn)行定制,代碼如下

PlaceholderST.output = new PlaceholderST.Output(){
        @Override
        public void write(Context ctx, Object value) throws IOException {
                //定制輸出
                ctx.byteWriter.writeString("ok"+value!=null?value.toString:"");
        }
};

如果PlaceholderST靜態(tài)變量output 不為null,將使用output 來輸出

3.16. 定制模板引擎

Beetl在線體驗(http://ibeetl.com/beetlonline/)面臨一個挑戰(zhàn),允許用戶輸入任何腳本做練習(xí)或者分享代碼。但又需要防止用戶輸入惡意的代碼,如

<%
for(var i=0;i<10000000;i++){
        //其他代碼
}
%>

此時,需要定制模板引擎,遇到for循環(huán)的時候,應(yīng)該限制循環(huán)次數(shù),譬如,在線體驗限制最多循環(huán)5次,這是通過定義替換GeneralForStatement類來完成的,這個類對應(yīng)了for(exp;exp;exp) ,我們需要改成如下樣子:

class RestrictForStatement extends GeneralForStatement{
        public RestrictForStatement(GeneralForStatement gf){
                super(gf.varAssignSeq, gf.expInit, gf.condtion, gf.expUpdate, gf.forPart, gf.elseforPart, gf.token);
        }

        public void execute(Context ctx){
                if (expInit != null){
                        for (Expression exp : expInit){
                                exp.evaluate(ctx);
                        }
                }
                if (varAssignSeq != null){
                        varAssignSeq.execute(ctx);
                }

                boolean hasLooped = false;
                int i = 0;
                for (; i < 5; i++){
                        boolean bool = (Boolean) condtion.evaluate(ctx);
                        if (bool){
                                hasLooped = true;
                                forPart.execute(ctx);
                                switch (ctx.gotoFlag){
                                        case IGoto.NORMAL:
                                                break;
                                        case IGoto.CONTINUE:
                                                ctx.gotoFlag = IGoto.NORMAL;
                                                continue;
                                        case IGoto.RETURN:
                                                return;
                                        case IGoto.BREAK:
                                                ctx.gotoFlag = IGoto.NORMAL;
                                                return;
                                }
                        }else{
                                break;
                        }
                        if (this.expUpdate != null){
                                for (Expression exp : expUpdate){
                                        exp.evaluate(ctx);
                                }
                        }
                }

                if (i >= 5){
                        try{
                                ctx.byteWriter.writeString("--Too may Data in loop,Ignore the left Data  for Online Engine--");
                                ctx.byteWriter.flush(); 
                        } catch (IOException e){
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }
        }

        @Override
        public void infer(InferContext inferCtx){
                super.infer(inferCtx);
        }

}

盡管上面代碼很復(fù)雜,但實際上是改寫了原來的GeneralForStatement,將原來的24行while(true) 替換成for (; i < 5; i++) 用來控制最大循環(huán),并且62行檢測如果循環(huán)退出后,i等于5,則提示Too Many Data in Loop.

現(xiàn)在需要將此類替換原有的GeneralForStatement,

public class OnlineTemplateEngine extends DefaultTemplateEngine{
        public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
                Program program = super.createProgram(resource, reader, textMap, cr, gt);
                modifyStatemetn(resource,program,gt);
                return program;
        }
        private void modifyStatemetn(Resource resource,Program program,GroupTemplate gt){
                Statement[] sts = program.metaData.statements;
                StatementParser parser = new StatementParser(sts, gt, resource.getId());
                parser.addListener(WhileStatement.class, new RestrictLoopNodeListener());
                parser.addListener(GeneralForStatement.class, new RestrictLoopNodeListener());
                parser.parse();
        }
}

繼承FastRuntimeEngine有所不同,因為改引擎會copy出一個腳本做分析優(yōu)化,因此,倆個腳本都需要做修改

public class OnlineTemplateEngine extends FastRuntimeEngine{
        public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
                FilterProgram program = (FilterProgram)super.createProgram(resource, reader, textMap, cr, gt);
                modifyStatemetn(resource,program,gt);
                modifyStatemetn(resource,program.getCopy(),gt);
                return program;
        }
}
class RestrictLoopNodeListener implements Listener{
        @Override
        public Object onEvent(Event e){
                Stack stack = (Stack) e.getEventTaget();
                Object o = stack.peek();
                if (o instanceof GeneralForStatement){
                        GeneralForStatement gf = (GeneralForStatement) o;
                        RestrictForStatement rf = new RestrictForStatement(gf);
                        return rf;
                }else{
                        return null;
                }
        }
}

該監(jiān)聽器返回一個新的RestrictForStatement 類,用來替換來的GeneralForStatement。如果返回null,則不需替換。這通常發(fā)生在你僅僅通過修改該類的某些屬性就可以的場景

完成這些代碼后,在配置文件中申明使用新的引擎

ENGINE=org.bee.tl.online.OnlineTemplateEngine

這樣就完成了模板引擎定制。

3.17. 直接運行Beetl腳本

Beetl模板本質(zhì)上會轉(zhuǎn)化為Beetl腳本來執(zhí)行,這點跟jsp轉(zhuǎn)為servlet來執(zhí)行類似。GroupTemplate提供方法可以直接執(zhí)行Beetl腳本

  • public Map runScript(String key, Map<String, Object> paras) throws ScriptEvalError
  • public Map runScript(String key, Map<String, Object> paras, Writer w) throws ScriptEvalError
  • public Map runScript(String key, Map<String, Object> paras, Writer w, ResourceLoader loader) throws ScriptEvalError

key為資源名,paras為腳本的全局變量,w可選參數(shù),如果執(zhí)行腳本有輸出,則輸出到w里,loader參數(shù)可選,如果指定,則使用此laoder加載腳本

執(zhí)行腳本完畢后,返回到Map里的值可能包含如下:

  • 模板的頂級的臨時變量,key為臨時變量名
  • return 值將返回到map里 ,key為return

如下腳本(此時就不需要腳本定界符了)

var a = 1;
var b = date();
var c = '2';
return a+1;

調(diào)用runScript后,map里將返回key分別為a,b,c,return。 值分別為1,當(dāng)前日期,字符串'2,以及3。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內(nèi)容