zoukankan      html  css  js  c++  java
  • Beetl学习总结(3)——高级功能

    3.1. 配置GroupTemplate

    Beetl建议通过配置文件配置配置GroupTemplate,主要考虑到未来可能IDE插件会支持Beetl模板,模板的属性,和函数等如果能通过配置文件获取,将有助于IDE插件识别。 配置GroupTemplate有俩种方法

    • 配置文件: 默认配置在/org/beetl/core/beetl-default.properties 里,Beetl首先加载此配置文件,然后再加载classpath里的beetl.properties,并用后者覆盖前者。配置文件通过Configuration类加载,因此加载完成后,也可以通过此类API来修改配置信息

    • 通过调用GroupTemplate提供的方法来注册函数,格式化函数,标签函数等

    配置文件分为三部分,第一部分是基本配置,在第一节讲到过。第二部分是资源类配置,可以在指定资源加载类,以及资源加载器的属性,如下

    1
    2
    3
    4
    5
    6
    RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
    #资源配置resource后的属性只限于特定ResourceLoader
    #classpath 根路径
    RESOURCE.root= /
    #是否检测文件变化
    RESOURCE.autouCheck= true
    

    第一行指定了类加载器,第二行指定了模板根目录的路径,此处/ 表示位于classpath 根路径下,第三行是否自动检测模板变化,默认为true,开发环境下自动检测模板是否更改。关于如何如何自定义ResouceLoader,请参考下一章

    配置文件第三部分是扩展部分,如方法,格式化函数等

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #####  扩展 ##############
    ## 内置的方法
    FN.date = org.beetl.ext.fn.DateFunction
    FN.nvl = org.beetl.ext.fn.NVLFunction
    .................
    ##内置的功能包
    FNP.strutil = org.beetl.ext.fn.StringUtil
    
    ##内置的格式化函数
    FT.dateFormat =  org.beetl.ext.format.DateFormat
    FT.numberFormat =  org.beetl.ext.format.NumberFormat
    .................
    
    ##内置的默认格式化函数
    FTC.java.util.Date = org.beetl.ext.format.DateFormat
    FTC.java.sql.Date = org.beetl.ext.format.DateFormat
    
    ## 标签类
    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,FNP前缀表示FunctionPackage,FT表示format函数,FTC表示类的默认Format函数,TAG表示标签类。Beetl强烈建议通过配置文件加载扩展。以便随后IDE插件能识别这些注册函数

    3.2. 自定义方法

    3.2.1. 实现Function

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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方法有俩个参数,第一个是数组,这是由模板传入的,对应着模板的参数,第二个是Context,包含了模板的上下文,主要提供了如下属性

    • byteWriter 输出流

    • template 模板本身

    • gt GroupTemplate

    • globalVar 该模板对应的全局变量

    • byteOutputMode 模板的输出模式,是字节还是字符

    • safeOutput 模板当前是否处于安全输出模式

    • 其他属性建议不熟悉的开发人员不要乱动

    1 call方法要求返回一个Object,如果无返回,返回null即可

    2 为了便于类型判断,call方法最好返回一个具体的类,如date函数返回的就是java.util.Date

    3 call方法里的任何异常应该抛出成Runtime异常

    3.2.2. 使用普通的java类

    尽管实现Function对于模板引擎来说,是效率最高的方式,但考虑到很多系统只有util类,这些类里的方法仍然可以注册为模板函数。其规则很简单,就是该类的所有public方法。如果需还要Context 变量,则需要在方法最后一个参数加上Context即可,如

    1
    2
    3
    4
    5
    6
    7
    8
    public class util
    {
    
            public String print(Object a, Context ctx)
            {
                    ...............
    
            }
    

    注意

    1 从beetl效率角度来讲,采用普通类效率不如实现Function调用

    2 采用的普通java类尽量少同名方法。这样效率更低。beetl调用到第一个适合的同名方法。而不像java那样找到最匹配的

    3 方法名支持可变数组作为参数

    4 方法名最后一个参数如果是Context,则beetl会传入这个参数。

    3.2.3. 使用模板文件作为方法

    可以不用写java代码,模板文件也能作为一个方法。默认情况下,需要将模板文件放到Root的functions目录下,且扩展名为.html(可以配置文件属性来修改此俩默认值) 方法参数分别是para0,para1…..

    如下root/functions/page.fn

    1
    2
    3
    4
    5
    <%
    //para0,para1 由函数调用传入
    var current = para0,total = para1,style=para2!'simple'
    %>
    当前页面 ${current},总共${total}
    

    则在模板中

    1
    2
    3
    <%
    page(current,total);
    %>
    

    允许使用return 表达式返回一个变量给调用者,如模板文件functions ow.html

    1
    2
    3
    <%
            return date();
    %>
    

    在任何模板里都可以调用:

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

    也可以在functions建立子目录,这样function则具有namespace,其值就是文件夹名

    3.3. 自定义格式化函数

    需要实现Format接口

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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 参数表示需要格式化的对象,pattern表示格式化模式,开发时候需要考虑pattern为null的情况

    也可以实现ContextFormat 类抽象方法,从而得到Context,获取外的格式化信息。

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

    3.4. 自定义标签

    标签形式有俩种,一种是标签函数,第二种是html tag。第二种实际上在语法解析的时候会转化成第一种,其实现是HTMLTagSupportWrapper,此类将会寻找root/htmltag目录下同名的标签文件作为模板来执行。类似普通模板一样,在此就不详细说了

    3.4.1. 标签函数

    标签函数类似jsp2.0的实现方式,需要实现Tag类的render方法即可

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    public class DeleteTag extends Tag
    {
    
            @Override
            public void render()
            {
                    // do nothing,just ignore body
                    ctx.byteWriter.write("被删除了,付费可以看")
    
            }
    
    }
    

    如上一个最简单的Tag,将忽略tag体,并输出内容

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    public class XianDeDantengTag extends Tag
    {
    
            @Override
            public void render()
            {
                    doBodyRender();
    
            }
    
    }
    

    此类将调用父类方法doBodyRender,渲染tag body体

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    public class CompressTag extends Tag
    {
    
            @Override
            public void render()
            {
                    BodyContent  content = getBodyContent();
                    String content = content.getBody();
                    String zip = compress(conent);
                    ctx.byteWriter.write(zip);
    
            }
    
    }
    

    此类将调用父类方法getBodyContent ,获得tag body后压缩输出

    tag类提供了如下属性和方法供使用

    • args 传入标签的参数

    • gt GroupTemplate

    • ctx Context

    • bw 当前的输出流

    • bs 标签体对应的语法树,不熟悉勿动

    3.5. 自定义虚拟属性

    可以为特定类注册一个虚拟属性,也可以为一些类注册虚拟属性

    • public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 实现VirtualClassAttribute方法可以为特定类注册一个需要属性,如下代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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类的所有虚拟属性将执行eval方法,此方法根据年纪属性来输出对应的描述。

    • public void registerVirtualAttributeEval(VirtualAttributeEval e) 为一些类注册需要属性,VirtualAttributeEval.isSupport方法将判断是否应用虚拟属性到此类

    如下是虚拟属性类的定义

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    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配置了一个默认的资源加载器,如果通过gt.getTemplate(key),将调用默认的ResourceLoader,获取模板内容,然后转化为beetl脚本放入到缓存里。你也可以传入额外的资源管理器加载模板,通过调用gt.getTemplate(key,otherLoader)来完成;

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    GroupTemplate gt = new GroupTemplate(conf,fileLoader)
    //自定义,参考下一节
    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;
    }
    

    对于更复杂的模板资源来源,也可以自定义一个资源加载来完成,参考下一节

    3.7. 自定义资源加载器

    如果模板资源来自其他地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则需要自定义一个资源加载器。资源加载器需要实现ResourceLoader类。如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public interface ResourceLoader
    {
    
            /**
             * 根据key获取Resource
             *
             * @param key
             * @return
             */
            public Resource getResource(String key);
    
            /** 检测模板是否更改,每次渲染模板前,都需要调用此方法,所以此方法不能占用太多时间,否则会影响渲染功能
             * @param key
             * @return
             */
            public boolean isModified(Resource key);
    
            /**
             * 关闭ResouceLoader,通常是GroupTemplate关闭的时候也关闭对应的ResourceLoader
             */
            public void close();
    
            /** 一些初始化方法
             * @param gt
             */
            public void init(GroupTemplate gt);
    
            /**  用于include,layout等根据相对路径计算资源实际的位置.
             * @param resource 当前资源
             * @param key
             * @return
             */
            public String getResourceId(Resource resource, String key);
    
    
    
    }
    

    如下是一个简单的内存ResourceLoader

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @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下的所有模板,并注册为方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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类需要实现OpenReader方法,以及isModified方法。对于模板内容存储在数据库中,openReader返回一个Clob,isModified 则需要根据改模板内容对应的lastUpdate(通常数据库应该这么设计)来判断模板是否更改

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    public abstract class Resource
    {
    
    
            /**
             * 打开一个新的Reader
             *
             * @return
             */
            public abstract Reader openReader();
    
            /**
             * 检测资源是否改变
             *
             * @return
             */
            public abstract boolean isModified();
    

    参考例子可以参考beetl自带的ResourceLoader

    3.8. 使用CompositeResourceLoader

    组合加载器,可以包含多个已有的ResourceLoader,如下代码创建一个包含俩个文件和内存的ResourceLoader

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    FileResourceLoader fileLoader1 = new FileResourceLoader(path1);
    FileResourceLoader fileLoader2 = new FileResourceLoader(path2);
    Map data = getData();
    // 根据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文件内容

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

    第2行仍然是由fileLoader1加载,但第3行以http:前缀开头,因此将fileLoader2加载path2+/xxx.html.xxx.html内容如下

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

    因为以db:开头,因此会采用MapResourceLoader加载,内容是key为db:1对模板

    3.9. 自定义错误处理器

    错误处理器需要实现ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);

    • beeExceptionos,模板各种异常

    • writer 模板使用的输出流。系统自带的并未采用此Writer,而是直接输出到控制台

    自定义错误处理可能是有多个原因,比如

    1 想将错误输出到页面而不是控制台

    2 错误输出美化一下,而不是自带的格式

    3 错误输出的内容做调整,如不输出错误行的模板内容,而仅仅是错误提示

    4 错误输出到日志系统里

    5 不仅仅输出日志,还抛出异常。默认自带的不会抛出异常,ReThrowConsoleErrorHandler 继承了ConsoleErrorHandler方法,打印异常后抛出

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler
    {
    
            @Override
            public void processExcption(BeetlException ex, Writer writer)
            {
    
                    super.processExcption(ex, writer);
                    throw ex;
    
            }
    
    }
    

    beetl 提供 ErrorInfo类来wrap BeetlException,转化为较为详细的提示信息,他具有如下信息

    • type 一个简单的中文描述

    • errorCode 内部使用的错误类型标识

    • errorTokenText 错误发生的节点文本

    • errorTokenLine 错误行

    • msg 错误消息,有可能没有,因为有时候errorCode描述的已经很清楚了

    • cause 错误的root 异常,也可能没有。

    BeetlException 也包含了一个关键信息就是 resourceId,即出错所在的模板文件

    3.10. 自定义安全管理器

    所有模板的本地调用都需要通过安全管理器校验,默认需要实现NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String method) 方法

    如下是默认管理器的实现方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class DefaultNativeSecurityManager implements NativeSecurityManager
    {
    
            @Override
            public boolean permit(String resourceId, Class c, Object target, String method)
            {
                    if (c.isArray())
                    {
                            //允许调用,但实际上会在在其后调用中报错。不归此处管理
                            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 以及模板变量来完成。模板变量能完成复杂的布局

    • 采用layout include

    1
    2
    3
    4
    5
    6
     <%
     //content.html内容如下:
     layout("/inc/layout.html"){%>
     this is 正文
     ..........
     <%}%>
    

    如上一个子页面将使用layout布局页面,layout 页面内容如下

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

    layoutContent 是默认变量,也可以改成其他名字,具体请参考layout标签函数

    全局变量总是能被布局用的页面所使用,如果布局页面需要临时变量,则需要显示的传入,如:

    1
    2
    3
     <%
     var user= model.user;
     include("/inc/header.html",{title:'这是一个测试页面',user:user}){} %>
    

    这样,title和user成为全局变量,能被header.html 及其子页面引用到

    • 继承布局:采用模板变量和include

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
     <%
            var jsPart = {
     %>
       web页面js部分
    
     <%};%>
    
     <%
            var htmlPart = {
     %>
       web页面html部分
    
     <%};
      include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){}
     %>
    

    layout.html页面如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <body>
    <head>
    ${jsSection}
    </head>
    <body>
    .......
    ${htmlSection}
    </body>
    

    3.13. 性能优化

    Beetl性能已经很快了,有些策略能更好提高性能

    • 使用二进制输出,此策略可以使模板在语法分析的时候将静态文本转化为二进制,省去了运行时刻编码时间,这是主要性能提高方式。但需要注意,此时需要提供一个二进制输出流,而不是字符流,否则性能反而下降

    • 使用FastRuntimeEngine,默认配置。 此引擎能对语法树做很多优化,从而提高运行性能,如生成字节码来访问属性而不是传统的反射访问。关于引擎,可能在新的版本推出更好的引擎,请随时关注。

    • 通过@type 来申明全局变量类型,这不能提高运行性能,但有助于模板维护

    • 自定义ResourceLoader的isModified必须尽快返回,因此每次渲染模板的时候都会调用此方法

    为什么Beetl性能这么好…………(待续)

    3.14. 分布式缓存模板

    Beetl模板引擎模板在同一个虚拟机里缓存Beetl 脚本。也可以将缓存脚本到其他地方,只要实现Cache接口,并设置ProgramCacheFactory.cache即可,这样GroupTemplate将从你提供的Cache中存取Beetl脚本

    此功能未被很好测试

    3.15. 定制输出

    占位符输出允许定制。如所有日期类型都按照某个格式化输出,而不需要显示的使用格式化输出,或者为了防止跨脚本站点攻击,需要对类型为String的值做检查等,不必使用格式化函数,可以直接对占位符输出进行定制,代码如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    PlaceholderST.output = new PlaceholderST.Output(){
    
                                            @Override
                                            public void write(Context ctx, Object value) throws IOException {
                                                    //定制输出
                                                    ctx.byteWriter.writeString("ok"+value!=null?value.toString:"");
    
                                            }
    
                                    };
    

    如果PlaceholderST静态变量output 不为null,将使用output 来输出

    3.16. 定制模板引擎

    Beetl在线体验(http://ibeetl.com:8080/beetlonline/)面临一个挑战,允许用户输入任何脚本做练习或者分享代码。但又需要防止用户输入恶意的代码,如

    1
    2
    3
    4
    5
    <%
    for(var i=0;i<10000000;i++){
            //其他代码
    }
    %>
    

    此时,需要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循5次,这是通过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,我们需要改成如下样子:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    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);
    
            }
    
    }
    

    尽管上面代码很复杂,但实际上是改写了原来的GeneralForStatement,将原来的24行while(true) 替换成for (; i < 5; i++) 用来控制最大循环,并且62行检测如果循环退出后,i等于5,则提示Too Many Data in Loop.

    现在需要将此类替换原有的GeneralForStatement,

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    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出一个脚本做分析优化,因此,俩个脚本都需要做修改

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    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;
            }
    
    
    }
    
    • StatementParser 是关键类,他允许对模板的Program进行解析,并替换其中的Statement。parser.addListener 方法接受俩个参数,第一个是需要找的类,第二个是执行的监听器。

    • 可以参考在线体验的源码:http://git.oschina.net/xiandafu/beetlonline/blob/master/src/org/bee/tl/online/OnlineTemplateEngine.java

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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;
                    }
            }
    

    该监听器返回一个新的RestrictForStatement 类,用来替换来的GeneralForStatement。如果返回null,则不需替换。这通常发生在你仅仅通过修改该类的某些属性就可以的场景

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

    1
    ENGINE=org.bee.tl.online.OnlineTemplateEngine
    

    这样就完成了模板引擎定制。

    3.17. 直接运行Beetl脚本

    Beetl模板本质上会转化为Beetl脚本来执行,这点跟jsp转为servlet来执行类似。GroupTemplate提供方法可以直接执行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可选参数,如果执行脚本有输出,则输出到w里,loader参数可选,如果指定,则使用此laoder加载脚本

    执行脚本完毕后,返回到Map里的值可能包含如下:

    • 模板的顶级的临时变量,key为临时变量名

    • return 值将返回到map里 ,key为return

    如下脚本(此时就不需要脚本定界符了)

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

    调用runScript后,map里将返回key分别为a,b,c,return。 值分别为1,当前日期,字符串'2,以及3.

  • 相关阅读:
    带妹入坑,她该怎样提高自己的编程能力?
    性能测试--cpu使用率过高怎么办
    loadrunner Controller 删除available scripts中无用脚本
    loadrunner 立即执行+定时执行设置
    loadrunner11 :脚本日志打印设置及举例说明
    loadrunner录制chrome脚本:页面无响应
    Error -27492: "HttpSendRequest" failed, Windows error code=12152 (invalid server response) and retry。。。
    loadrunner11错误:Error -27776: Server "wsg.cmszmail.ad" shut connection during attempt to negotiate SSL session [MsgId: MERR-27776]解决办法
    loadrunner11 错误:Error -26377: No match found for the requested parameter XXXX. web_custom_request(XXX) highest severity level was "ERROR",....... [MsgId: MMSG-26387]
    loadrunner11 执行bat批处理文件时获取bat文件所在路径 正确方式与采坑说明
  • 原文地址:https://www.cnblogs.com/zhanghaiyang/p/7212775.html
Copyright © 2011-2022 走看看