记一次在webx中velocity新建自定义指令的过程

  1. 云栖社区>
  2. 博客>
  3. 正文

记一次在webx中velocity新建自定义指令的过程

萧元 2016-08-15 09:41:11 浏览2577 评论0

摘要: ### 记一次在webx中velocity新建自定义指令的过程 ![screenshot](http://img2.tbcdn.cn/L1/461/1/b59c6597d3f90168126d63ebc63f967ef1dc348f) webx和velocity就不介绍了。 都很熟悉。本文是记录在webx中增加唉velocity自定义指令的方法。 起因是在velocity渲染模板的时候

记一次在webx中velocity新建自定义指令的过程

screenshot
webx和velocity就不介绍了。 都很熟悉。本文是记录在webx中增加唉velocity自定义指令的方法。
起因是在velocity渲染模板的时候,我们使用了#esc_noesc(variable)做转义,当这个渲染变量未定义时,变量会渲染为${content},而使用$!{variable}渲染简单字符变量时,若未定义则会被渲染为空字符串,这两个场景对于js变量的渲染都会引起js语法错误造成js无法tryCatch, 由于这种后端变量渲染的逻辑大多用于主干代码, 一旦出现错误就会引起白屏,整个页面挂掉(不要问我怎么知道。。) 如下图:
variableError
escError

于是我想到能否将所有针对js的输出变量都赋有一个"''"。 避免页面直接白屏,且写大量的errorLog触发报警。
我们知道, velocity中可以设置自定义指令。
我们定义好一个基于Directive的子类,然后在directive.properties中配置即可。
这是一个简单的自定义指令,作用是获取context某个属性的值:

class CustomVelocityDirective extends Directive{

    static String methodName = 'getValue';

    @Override
    public String getName() {
        return methodName ;  // 指令名 对应到velocity模板中的 methodName()
    }
    @Override
    public int getType() {
        return LINE; // 指令类型 包括 LINE/BLOCK
    }

    @Override
    public void init(RuntimeServices rs, InternalContextAdapter context, Node node) throws TemplateInitException {
        super.init(rs, context, node); // 初始化模板
    }

    @Override
    public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {    
        SimpleNode getChildren = (SimpleNode) node.jjtGetChild(0); //获取指令传参
        String variableName = (String)sn_region.value(context); // 获取到传参的文字 or 对象
        Object value = context.get(variableName).toString();  //获取该key对应到context的属性
        writer.write(value); // 打印输出内容
        return true;  //打印成功
    }
}

配置directive.properties属性,声明这个指令

userdirective=xxx.xxx.YourDirectiveClass  // 可用逗号分隔指定多个

使用时:

// some HTML..   
#getValue("name")   // 打印value  by: context.put("name", "value")
// some HTML..   

参考文章

webx中的做法

以上是Velocity的做法, 看起来还比较容易, 简单明了。 然而现实很骨感, 现实是我们用的是webx。 当我们面对webx,一切都不那么好了。

首先我试着在我们的properties文件中增加userdirective=xxx.xxx.YourDirectiveClass, 当然, 一切并不成功。看来照搬velocity的做法在webx中并不合适。

接着在网上看到一篇文章 (在velocity中自定义标签) 在velocity中自定义标签 应该是唯一一篇提到了webx设置velocity自定义标签的文章。 照着做了一遍, 可是在如何声明Schema文件的地方有点含糊。 想起了我们在webx中用到过escape指令, 于是找相应的声明代码, 最终找到类似的EscapeSupport。 依葫芦画瓢,照着做了,也大致摸清了webx中增加自定义velocity插件的方法。

Velocity:

设置: userdirective=xxx.xxx.YourDirectiveClass

Webx:

新建parser文件的声明

在META-INF下新建services-template-engines-velocity-plugins.bean-definition-parsers 文件,指定Schema文件和Parser, 内容是:

custom-directive=xxxx.xxx.xxx.xxxParser   
新建Schema文件

按照META-INF/services/template/engines/velocity/plugins 目录,新建一个xsd文件,声明Schema,(由于我没有其他属性,所以Schema文件异常简单):

<xsd:element name="variable-parser" type="VariableParserType">
    <xsd:annotation>
        <xsd:documentation><![CDATA[some ducumentation]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
    <xsd:complexType name="VariableParserType" />
定义Parser类:
class VariableDefinitionParser extends AbstractSingleBeanDefinitionParser<VariableParserSupport> {
    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    }
}
定义VelocityPlugin的实现类:
public class VariableParserSupport implements VelocityPlugin {
    public void init(VelocityConfiguration configuration) throws Exception {
        configuration.getProperties().addProperty("userdirective", CustomVelocityDirective.class.getName());
    }
    public Resource[] getMacros() throws IOException {
        return new Resource[] { };
    }
}
在webx-component-and-root.xml中增加声明
<services:template searchExtensions="true">
        <tpl-engines:velocity-engine templateEncoding="UTF-8" strictReference="false" path="/templates/${component}">
            <global-macros>
                <name>global/*.vm</name>
            </global-macros>
            <plugins>
                <vm-plugins:custom-directive />   // 写入我们自定义的标签的
                <vm-plugins:escape-support defaultEscape="html">
                 I   <noescape>
                        <if-matches pattern="^control\." />
                        <if-matches pattern="^screen_placeholder" />
                        <if-matches pattern="^stringEscapeUtil\.escape" />
                        <if-matches pattern="^csrfToken\.(get)?hiddenField" />
                        <if-matches pattern="^tbToken\.(get)?hiddenField" />
                        <if-matches pattern="^securityUtil\.(richtext|jsEncode|ignoretext)" />
                    </noescape>
                </vm-plugins:escape-support>
            </plugins>
        </tpl-engines:velocity-engine>
        <tpl-engines:freemarker-engine templateEncoding="UTF-8" path="/templates/${component}"/>
        <tpl-engines:jsp-engine path="/templates/${component}"/>
    </services:template>

在做到倒数第二不,声明Support类的时候, 我看到画龙点睛的一句:

configuration.getProperties().addProperty("userdirective", CustomVelocityDirective.class.getName());  

而我们做了这么多,就是为了这一句。 对应到velocity就只是这行代码::

userdirective=xxx.xxx.YourDirectiveClass

最后知道真相的我眼泪掉下来。。。。。。

当然,velocity的自定义指令在webx中是作为一个velocity插件的方式使用。 在webx中由于多了一套约定的Schame,导致在webx中增加自定义组件变得复杂数倍,当然功能也有所增强(例如支持宏)。
并且针对我想要的failover场景,也许新增自定义指令的方法并不是最优解,也许有其他成熟的webx解决方案。 也希望有人能指出,一起交流。

最后感谢下 @贾少天 的帮助。

参考资料:

1 WebX文档

  1. velocity自定义标签和指令variableError
【云栖快讯】一站式开发者服务,海量学习资源免费学  详情请点击

网友评论