在Struts2中使用Velocity模板时,如何以相对与Web工程的路径来配置模板资源文件路径这个问题网上千篇一律的来自Velocity官方文档。官方文档中指出如果是Web工程的话,模板的相对路径是工程根路径,今天在使用的时候有如下配置:
Velocity.properties(默认在WEB-INF下):
1
2
3
4
5
6
7
8
|
resource.loader =file, class
class.resource.loader.description = Velocity Classpath Resource Loader
class.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
file.resource.loader.description = Velocity File Resource Loader
file.resource.loader.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
file.resource.loader.path = WEB-INF\template
file.resource.loader.cache = false
file.resource.loader.modificationCheckInterval = 20
|
使用了两种资源加载方式:file 和 class.
1. 测试1(class方式):
在Web工程的src目录下有vm.test.vm模板,注意test.vm放置在了类路径上,然后在Struts的配置文件中配置Action结果视图:
1
2
3
|
<action name="vm" class="com.action.VmPathAction">
<result name="success" type="velocity">/vm/test.vm</result>
</action>
|
这里的结果视图路径为:/vm/test.vm(注意“/”不能省略),访问Action,模板信息正常显示。
2.测试2(file方式)
在Web工程下的WEB-INF下创建了template/hello.vm,在Struts的配置文件中配置Action结果视图:
1
2
3
|
<action name="hello" class="com.action.HelloAction">
<result name="success" type="velocity">hello.vm</result>
</action>
|
通过配置Velocity的资源加载器FileResourceLoader和loader.path,始终找不到hello.vm; 接着loader.path使用绝对路径:<tomcat_webapp>/webproject/WEB-INF/template,还是没有找到hello.vm。
3.问题
类加载的方式是没有问题的,文件加载方式通过如上配置是不可行了,并且参考Velocity手册中指出的Web工程配置方式也是不可行,由于是在Struts2中使用的Velocity,于是在Struts2的手册中一探究竟。
Struts2中有一个velocity.properties:
1
2
|
# Velocity Macro libraries.
velocimacro.library = action-default.vm, tigris-macros.vm, myapp.vm
|
从内容上看,velocity中配置了Struts2自定义的宏,当然我们也可以配置自己的宏
Struts2的struts.properites文件
1
2
3
4
5
6
|
### Location of velocity.properties file. defaults to velocity.properties
struts.velocity.configfile = velocity.properties
### Comma separated list of VelocityContext classnames to chain to the StrutsVelocityContext
struts.velocity.contexts =
### Location of the velocity toolbox
struts.velocity.toolboxlocation=
|
从内容上看,struts2中可以重新定义velocity文件的路径,名称信息;多Context类的分隔符,toolbox的位置。
然后,从这两个配置文件中并没能找到问题的入口,回到问题的开始,在引入了Struts2MVC框架的Web工程,配置了Velocity的FileResourceLoader之后,Velocity不再使用默认配置,Struts2的Action结果中找不到视图,由此可以可以推断,视图层出来问题。
Struts2MVC的V
Struts2提供了多种视图展示方式比如:jsp标签, Freemarker模板,Velocity模板等,这里主要一探Velocity模板究竟是如何加载的。
下图是Struts的view包:
Struts2视图层的标签库提供了TagLibraary接口,然后在DefaultTagLibrary提供了FreeMarker和Velocity的实现。
velocity包下的VelocityManager类是用来管理Velocity结果类型的环境。通过阅读该类的代码发现该类的loadConfiguration(ServletContext context)正是Struts2框架下Web工程中加载velocity.properties文件的流程。
基本流程是:
判断Context(不为null,说明Web容器已经启动)
创建velocity配置,Struts2横切的方式对velocity配置进行了处理
判断Struts2的属性配置中的struts.velocity.configfile配置类确定velocity配置,如果存在采用配置文件,不存在采用默认velocity.properties文件
按照三种方式顺序搜索(1.相对context path, 2.相对WEB-INF, 3.classpath).
搜索到velocity.properties文件之后,读取信息到velocity配置中去(合理的进行用户自定义覆盖程序默认配置)
问题出现的场景:未有配置struts2的属性;velocity.properties放置在WEB-INF下;请求有响应tomcat容器正常启动;看来问题出在ii上。
列出Struts对velocity.properties的配置进行重写的环节代码:
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
|
/**
* once we've loaded up the user defined configurations, we will want to apply Struts specification configurations.
* <ul>
* <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file,
* class loader for unpackaed wars and a straight class loader otherwise</li>
* <li>we need to define the various Struts custom user directives such as #param, #tag, and #bodytag</li>
* </ul>
*
* @param context
* @param p
*/
private
void
applyDefaultConfiguration(ServletContext context, Properties p) {
// ensure that caching isn't overly aggressive
/**
* Load a default resource loader definition if there isn't one present.
* Ben Hall (22/08/2003)
*/
if
(p.getProperty(Velocity.RESOURCE_LOADER) ==
null
) {
p.setProperty(Velocity.RESOURCE_LOADER,
"strutsfile, strutsclass"
);
}
/**
* If there's a "real" path add it for the strutsfile resource loader.
* If there's no real path and they haven't configured a loader then we change
* resource loader property to just use the strutsclass loader
* Ben Hall (22/08/2003)
*/
if
(context.getRealPath(
""
) !=
null
) {
p.setProperty(
"strutsfile.resource.loader.description"
,
"Velocity File Resource Loader"
);
p.setProperty(
"strutsfile.resource.loader.class"
,
"org.apache.velocity.runtime.resource.loader.FileResourceLoader"
);
p.setProperty(
"strutsfile.resource.loader.path"
, context.getRealPath(
""
));
p.setProperty(
"strutsfile.resource.loader.modificationCheckInterval"
,
"2"
);
p.setProperty(
"strutsfile.resource.loader.cache"
,
"true"
);
}
else
{
// remove strutsfile from resource loader property
String prop = p.getProperty(Velocity.RESOURCE_LOADER);
if
(prop.indexOf(
"strutsfile,"
) != -
1
) {
prop = replace(prop,
"strutsfile,"
,
""
);
}
else
if
(prop.indexOf(
", strutsfile"
) != -
1
) {
prop = replace(prop,
", strutsfile"
,
""
);
}
else
if
(prop.indexOf(
"strutsfile"
) != -
1
) {
prop = replace(prop,
"strutsfile"
,
""
);
}
p.setProperty(Velocity.RESOURCE_LOADER, prop);
}
/**
* Refactored the Velocity templates for the Struts taglib into the classpath from the web path. This will
* enable Struts projects to have access to the templates by simply including the Struts jar file.
* Unfortunately, there does not appear to be a macro for the class loader keywords
* Matt Ho - Mon Mar 17 00:21:46 PST 2003
*/
p.setProperty(
"strutsclass.resource.loader.description"
,
"Velocity Classpath Resource Loader"
);
p.setProperty(
"strutsclass.resource.loader.class"
,
"org.apache.struts2.views.velocity.StrutsResourceLoader"
);
p.setProperty(
"strutsclass.resource.loader.modificationCheckInterval"
,
"2"
);
p.setProperty(
"strutsclass.resource.loader.cache"
,
"true"
);
// components
StringBuilder sb =
new
StringBuilder();
for
(TagLibrary tagLibrary : tagLibraries) {
List<Class> directives = tagLibrary.getVelocityDirectiveClasses();
for
(Class directive : directives) {
addDirective(sb, directive);
}
}
String directives = sb.toString();
String userdirective = p.getProperty(
"userdirective"
);
if
((userdirective ==
null
) || userdirective.trim().equals(
""
)) {
userdirective = directives;
}
else
{
userdirective = userdirective.trim() +
","
+ directives;
}
p.setProperty(
"userdirective"
, userdirective);
}
|
阅读完这一个方法之后,可以清楚的看到Struts对velocity的配置文件做了处理。
下面分析一下这段代码:
1.参数Properties(p)
这里要看看velocity.properties加载流程,然后看调用applyDefaultConfiguration方法传人的参数。
1
2
3
|
Properties properties =
new
Properties();
// now apply our systemic defaults, then allow user to override
applyDefaultConfiguration(context, properties);
|
由此可以看出创建的Properties对象次数没有值。
1
|
p.getProperty(Velocity.RESOURCE_LOADER) ==null
|
Struts将为resource.loader添加strutsfile, strutsclass;
2.判断context.getRealPath
1
|
context.getRealPath("") != null //成立
|
结果是成立的。
因此struts2为velocity配置resource.loader=sturtsfile的参数;
至于配置resource.loader=strutsclass的参数,注释上写的很清楚(为了从Web Path下的classpath下获取Struts Taglib中的模板)。
如果是不成立的话,可以看到else块中在对resource.loader=strutsfile的strutsfile进行清理。
3.解决问题
很遗憾的是在文中开始出现访问不到FileResourceLoader加载的模板,问题是由于配置了resource.loader,且resource.loader=file, 回到调用applyDefaultConfiguration方法的方法,看看applyDefaultConfiguration处理完Properties对象之后,还做了什么处理?
按照velocity.properties的加载流程,当velocity.properties加载之后,执行了:
1
2
3
4
5
6
7
|
// if we've got something, load 'er up
if
(in !=
null
) {
if
(LOG.isInfoEnabled()) {
LOG.info(
"Initializing velocity using "
+ resourceLocation);
}
properties.load(in);
}
|
可以看到properties读取velocity.properties文件后会产生覆盖操作(前提是resource.loader等信息在velocity.properties中配置过),直接导致最开始Struts赋值给properties的resource.loader改变了,因此后面的参数配置自然就无效了。
另外loadConfiguration下面还有一段代码:
1
2
3
4
5
6
7
8
|
// overide with programmatically set properties
if
(
this
.velocityProperties !=
null
) {
Iterator keys =
this
.velocityProperties.keySet().iterator();
while
(keys.hasNext()) {
String key = (String) keys.next();
properties.setProperty(key,
this
.velocityProperties.getProperty(key));
}
}
|
最后一步:VelocityManager类的velocityProperties属性对最终放回的properites对象进行符合规则地重写。
写到这里,问题自然迎刃而解。
不配置resource.loader(注:模板路径相对于WEB工程根路径)
新配置resource.loader,由于velocity默认配置了resource.loader=file,因此需要覆盖此配置,并且给resource.loader添加上strutsfile, strutsclass, 然后就可以重新设置loader.path.
在已有的resource.loader配置中添加strutsfile, strutsclass(如何存在class配置, strutsclass可以不配置 )
另外需要注意的是:strutsfile, strutsclass这两个resource.loader的名称是不能变了,但是其它命名是可以改变(如:file, class),其实际的运行取决于loader.class:
1
|
resource.loader=file
|
1
|
<file>.resource.loader.class = <org.apache.velocity.runtime.resource.loader.FileResourceLoader>
|
1.中的Action结果视图路径配置问题:
本想着既然配置了模板的路径,那么在result中直接写相对模板路径的模板视图文件名就可以了,实际操作中发现是不可以的。在struts的Action结果配置中有如下配置:
1
2
3
4
5
|
<
action
name
=
"hello"
class
=
"com.action.HelloAction"
>
<
result
name
=
"success"
type
=
"velocity"
>
<
param
name
=
"location"
>/WEB-INF/template/hello.vm</
param
>
</
result
>
</
action
>
|
1
2
3
4
5
|
<
action
name
=
"hello"
class
=
"com.action.HelloAction"
>
<
result
name
=
"success"
type
=
"velocity"
>
/WEB-INF/template/hello.vm
</
result
>
</
action
>
|
这两种配置都是有效的。注意:"/"是不可以缺少的。
下面看一下VelocityResult类下的getTemplate方法,就能说明“/”的重要性。
1
2
3
4
5
6
7
8
|
protected
Template getTemplate(ValueStack stack, VelocityEngine velocity, ActionInvocation invocation, String location, String encoding)
throws
Exception {
if
(!location.startsWith(
"/"
)) {
location = invocation.getProxy().getNamespace() +
"/"
+ location;
}
Template template = velocity.getTemplate(location, encoding);
return
template;
}
|
假设缺少:“/”, 如果namespace为“”可以很侥幸的获取到template;否则template的location变为namespace+/+location(namespace/WEB-INF/template/hello.vm),这样以来找不到模板就很自然了。此外Struts2的其它的StrutsResultSupport的子类或者Result类的实现类都对location的值做了处理。
PS:框架涉及的面多了,文档在边边角角似乎有点乏力,只能剖析源码解决疑问。
本文转自 secondriver 51CTO博客,原文链接:http://blog.51cto.com/aiilive/1424635,如需转载请自行联系原作者