velocity自定义指令不生效问题解决之旅
一、背景现象
为了支持灵活的、可自定义的脱敏规则,工程拟采用velocity实现该目的,为此,自定义了:
- mask、substr两个指令,其中
- mask实现
public class MaskDirective extends Directive { @Override public String getName() { return "mask"; } @Override public int getType() { return LINE; } @Override public boolean render(InternalContextAdapter ctx, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { //fieldName SimpleNode snField = (SimpleNode) node.jjtGetChild(0); String maskChar = (String)snField.value(ctx); snField = (SimpleNode) node.jjtGetChild(1); int count = (int)snField.value(ctx); StringBuilder buff = new StringBuilder(); for(int i = 0; i < count; i++) { buff.append(maskChar); } writer.write(buff.toString()); return true; } }
- substr实现
public class SubstrDirective extends Directive { @Override public String getName() { return "substr"; } @Override public int getType() { return LINE; } @Override public boolean render(InternalContextAdapter ctx, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { //fieldName SimpleNode snField = (SimpleNode) node.jjtGetChild(0); String fieldValue = (String)snField.value(ctx); if(fieldValue == null || fieldValue.length() == 0){ return false; } int items = node.jjtGetNumChildren(); int startIdx = Integer.MIN_VALUE; int endIdx = fieldValue.length(); if(items < 1){ return false; } try{ snField = (SimpleNode) node.jjtGetChild(1); startIdx = (int)snField.value(ctx); if(items > 2){ snField = (SimpleNode) node.jjtGetChild(2); endIdx = (int)snField.value(ctx); if(endIdx > fieldValue.length()){ endIdx = fieldValue.length(); } } if(startIdx >= endIdx){ return false; } StringBuilder buff = new StringBuilder(); if(items > 2){ writer.write(fieldValue.substring(startIdx,endIdx)); }else{ if(startIdx < 0){ startIdx = startIdx + fieldValue.length(); } writer.write(fieldValue.substring(startIdx)); } return true; }catch(Exception e){ return false; } } }
- velocity初始化方法:
public final class VelocityHelper { private Set<String> userExtDirective ; private final static String K_USERDIRECTIVE = "userdirective"; private final static VelocityHelper VH = new VelocityHelper(); private VelocityHelper(){ } public static final VelocityHelper getInstance(){ return VH; } /** * 初始化 */ public void init() throws Exception{ Properties prop = new Properties(); StringBuilder buff = new StringBuilder(); for(String s : userExtDirective){ buff.append(s.trim()).append(","); } LogHelper.trace("start VelocityHelper.init[" + buff.toString() + "]"); prop.setProperty(K_USERDIRECTIVE, buff.toString().substring(0, buff.length()-1)); Velocity.init(prop); LogHelper.trace("end VelocityHelper.init"); } /** * 根据报文模板和<code>VelocityContext</code>,渲染出实际的业务报文 * * @param context 业务上下文信息 * @param template 模板 * @return * @throws IOException */ public String evaluate(String template, final VelocityContext context) throws IOException { Writer writer = new StringWriter(); try { Velocity.evaluate(context, writer, StringUtil.EMPTY_STRING, template); return writer.toString(); } finally { IOUtils.closeQuietly(writer); } } /** * * @param userExtDirective * */ public void setUserExtDirective(Set<String> userExtDirective){ this.userExtDirective = userExtDirective; } }
部署到服务器后,以下表达式始终未得到替换:#substr($maskField, 0, 3)#mask('*', 4)#substr($maskField, -4)
二、原因分析
开始怀疑是配置等问题,经过以下方法尝试均为解决问题:
1、怀疑spring xml配置问题----未解决
2、硬编码指令,在构造函数塞入指令----未解决
无奈,登录服务器grep velocity 得到了以下的输出(其中之一),茅塞顿开。
2019-02-02 07:49:52,315 [ - /// - ] INFO assemble.VMRender - resource=class path resource [velocity/velocity.properties]
Velocity.ini及Velocity.evalute均是全局单一模式,既然有人捷足先登,自然不在做第二次初始化。
三、解决方案
采用VelocityEngine替换Velocity,实现jar包间隔离,独立管理自己的资源。
private VelocityEngine velocityEngine = null; velocityEngine = new VelocityEngine(prop); velocityEngine.evaluate(context, writer, StringUtil.EMPTY_STRING, template);
化零为整,化繁为简,持之以恒