最近使用velocity的宏进行类目树的展示,但是却发现了一些离奇的问题,发现velocity的宏没那么好使,表面现象是:宏里面没有独立的变量作用域,宏的参数传递是引用传递。关于这个问题,我专门做了个小测试。测试如下。
经过测试后发现基本和一开是的猜测一致。本来怀疑就是velocity宏的功能有限了,但是查了资料后发现其实并非如此。
1.宏的传参:
宏的参数其实是按名称传递的(pass by name),这种方式和我们java的函数值传参不同,velocimacro的参数传递有点类似引用传递。这种方式就是说只有当宏开始执行时候才去解析参数,加入参数是一个函数调用,并且在宏里面会有多次出现该参数,那么每次解析都调用一次。要想限制这种动态的效果,可以在调用宏之前用#set($tmp=$xx)来把xx参数先解析一遍。
#macro( callme $a )
$a $a $a
#end
##这里效果是:bar()被调用了3此,加入$foo是有状态的而bar()会改变其状态,那么将会改变3次。
#callme( $foo.bar() )
##这里就不会出现上述效果,只会执行一次bar()
#set($tmp = $foo.bar())
#callme( $tmp )
2.宏的变量作用域:
其实velocimacro的作用域是可以设置的。默认宏是能接受到调用者的环境的。就是外部变量在宏里面是可见的,并能被修改。要想让宏的作用域只在自己范围里面,那么可以将velocity的属性velocimacro.context.localscope设置为false即可。见上面代码的测试效果。
参考资料显示——“context.localscope属性用于定义#set指令影响在Velocimacro 中使用Velocity上下文的方式。当属性值为true时,Velocimacro将有效接收它自己本地的上下文。调用者的对象上下文关键字对Velocimacro来说不可见,转而变成使用来自Velocimacro内部的上下文,而且不复制回转给调用者。与此相反,当属性值为false时,将把调用者的上下文放到一个Velocimacro可达到的、可更改的地方。也就是说Velocimacro将使用调用者的上下文。默认值为false。”下面是具体测试过程:在eclipse中单独起了个java工程,java代码如下:其中包含了3个测试用例testVarAssign()用来测试velocity的变量赋值和宏的作用域的testMacroRec()用来测试velocity的递归用的
下面是具体测试过程:
在eclipse中单独起了个java工程,java代码如下:
其中包含了3个测试用例
testVarAssign()用来测试velocity的变量赋值和宏的作用域的
testMacroRec()用来测试velocity的递归用的
package org.nx.velocity;
import java.io.StringWriter;
import java.util.Properties;
import junit.framework.TestCase;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.junit.Test;
public class VariableTest extends TestCase {
private VelocityEngine ve = new VelocityEngine();
private Properties properties = new Properties();
private String VM_PATH;
{
VM_PATH = this.getClass().getResource("/vm").getPath();
properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, VM_PATH);
properties.setProperty(Velocity.VM_CONTEXT_LOCALSCOPE, "true");
try {
ve.init(properties);
} catch (Exception e) {
assertTrue(false);
}
}
@Test
public void testVarAssign() {
try {
VelocityContext context = new VelocityContext();
Template template = ve.getTemplate("testAssign.vm", "gbk");
StringWriter sw = new StringWriter();
template.merge(context, sw);
System.out.println(sw.toString());
// assertEquals("2", sw.toString());
} catch (Exception e) {
e.printStackTrace();
assertTrue(false);
}
}
@Test
public void testMacroRec() {
try {
VelocityContext context = new VelocityContext();
Template template = ve.getTemplate("testMacroRec.vm", "gbk");
StringWriter sw = new StringWriter();
template.merge(context, sw);
System.out.println(sw.toString());
} catch (Exception e) {
e.printStackTrace();
// assertTrue(false);
}
}
}
testAssign.vm代码如下:
#macro (change $var)
#set($var = 1)set var = 1 [var=$var]
#set($someVarOutside = 100)set someVarOutside = 100 [someVarOutside=$someVarOutside]
#end
case1:常规赋值
#set( $a = 1 )set a=1 [ a=$a ]
#set( $a = $a + 1)set a=a+1 [ a=$a ]
结论:正常
case2:多变量重复赋值
#set( $a1 = 1000 )set a1=1000 [a1=$a1]
#set( $a2 = $a1 )set a2=a1 [a2=$a2]
#set( $a3 = 2222 )set a3=2222 [a3=$a3]
#set( $a1 = $a3 )set a1=a3 [a1=$a1,a2=$a2]
结论:2变量没相互影响
case3:已初始化b=2,经过macro后,观察macro是否影响外部b的值
#set($b=2) set b=2 [b=$b]
begin-macro#change($b)
after-macro[ b=$b ]
结论:影响,估计是引用传递
case4:已初始化外部var=0,经过macro设置内部var=1后,观察macro外部var的值
#set($var = 0)set var=0 [var=$var]
begin-macro#change($b)
after-macro [var=$var]
结论:只要不是指向同一个变量,宏内外部变量相互不影响
case5:已初始化外部someVarOutside=0,经过macro设置内部someVarOutside=100后,观察macro外部someVarOutside的值
#set($someVarOutside = 1)set someVarOutside=1 [someVarOutside=$someVarOutside]
begin-macro#change($b)
after-macro [someVarOutside=$someVarOutside]
结论:宏里面的变量都是全局的(velocimacro.context.localscope=true下恰好相反)
testMacroRec.vm代码如下:
#macro(change $var)
#if ($var < 10)
#set($var = $var + 1)
$var
#set($tmp = 0)
$tmp=$var
#set($tmp = $var)
#change($tmp)
$var
#end
#end
#change(0)
参考文章:
某人根据《Mastering Apache Velocity》翻译的文章http://www.blogjava.net/kingwee/articles/235339.html
官网的说明文档http://people.apache.org/~henning/velocity/html/ch07.html