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