struts2的视图扩展是对xwork框架扩展的最复杂部分,而其中的模板抽象部分占据了大部分内容,今天我想在这里探讨一下模板最终路径的确定问题.在没有讨论这个问题之前先将struts2的视图结构稍作分析.
struts2的视图基础结构实际还是xwork中进行规范的,这个规范就是Result,在struts2中在视图进行扩充时实际就是给出多种形式的Result实现.例如dispatcher,freemarker,redirect,stream,plainText,json等等,并且这种扩充是无限的,作为一个Result实现它们在结构上是具有非常重要的地位,但并不代表这种实现就很复杂,它们的复杂度与它们的实现紧密相关.有得实现很简单,而有得并不是很简单.但在这些Result实现中有一类比较特别的实现主要以dispatcher,freemarker,velocity为代表它们有一个特点就是可以在所谓的"页面"上使用UI组件对象,关于UI组件的问题可以参考我写的另篇文章http://blog.csdn.net/zhongxiucheng/article/details/6774432,关于UI组件是struts2中比较复杂的部分,
这里有一个很重要的问题是不要将Result的dispatcher,freemarker,velocity实现与组件的dispatcher,freemarker,velocity实现混淆,jsp,freemarker,velocity它们只是三种相似的技术,只是它们可以同时用于实现Result与UI component两种抽象结构,这里两种结构虽然不相同,但是它们的联系是非常紧密的,UI组件是建立在Result实现基础之上的,但不要误认为在Result的dispatcher实现就只能使用jsp技术的UI组件,而Result的freemarker实现就只能使用freemarker技术的UI组件.理论上讲Result的在dispatcher,freemarker,velocity三种技术中的任何一种实现,都可以使用三种技术中任何一种UI组件实现.
谈到UI组件,就涉及模板,以及模板路径的问题.这里的模板路径与Result配置中的转发路径是不同的,转发路径就是普通的相对或绝对路径,而模板的路径主要由三个参量来决定:templateDir(模板目录),theme(主题),template(模板名称)而这三个参量又分别有默认参量:defaultTemplateDir,defaultUITheme,defaultTemplate其中defaultTemplateDir默认值是template;而defaultUITheme的默认值为xhtml;defaultTemplate分别是不同的UI组件中定义的比如TextField组件的defaultTemplate值为text.对于,其中从这六个参量就可以看出模板路径的配置相对宽松,可以进行灵活的配置,下面是关于模板参量相关的优先级的说明:
设置模板路径
一般在struts2中的struts.properties 文件中的struts.ui.templateDir属性配置模板路径,默认为template。
模板加载顺序
首先搜索web路径下面的的/template/主题名称/template.ftl
然后再找classpath下面的/template/主题名称/template.ftl
然后再使用struts2包中的主题
设置主题名称
struts.properties 文件
struts.ui.theme设置主题名称 默认为xhtml.
也可以通过struts.xml文件配置以上两个属性。通过constant属性。
上面两个参数配置的实际就是defaultTemplateDir与defaultUITheme的值,这两个参数最好不覆盖,如果要覆盖最好使用templateDir(模板目录),theme(主题),这两个参数
设置模板引擎
通过struts.ui.templateSuffix属性设置。
模板引擎一般为;
ftl:基于FreeMarker的模板引擎
vm:基于velocit的模板引擎
jsp:基于jsp的模板引擎
模板路径的指定(按照优先排序的)
UI标签指定模板的templateDir属性路径
page范围指定模板的templateDir属性路径
request范围指定模板的templateDir属性路径
session范围指定模板的templateDir属性路径
application范围指定板的templateDir属性路径
通过struts.xml中的constant来指定struts.ui.templateDir
主题的指定(按照优先排序的)
UI标签的theme属性指定
UI标签的外围的form标签的theme属性指定
page范围的名为theme属性指定
request范围的名为theme属性指定
session范围的名为theme属性指定
application范围的名为theme属性指定
通过struts.xml中的constant来指定struts.ui.theme
覆盖整个标签的主题只需改变form标签的theme属性
可以基于session的不同设置theme属性
改变整个程序的主题,通过struts.xml 指定UI的theme属性
关于上面两个参量templateDir与theme当Result实现不是jsp时page范围是不可用的;另外request范围不要与request参数混淆,也就是说在请求中前台向后台传递了参数theme并不代表request范围中存在theme变量,至于前台前后台传递了参数,而在request范围中可以获取其值的原因,可以参考最后的关于StrutsRequestWrapper分析,关于上面优先级的设定可以参考UIBean中的代码,下面是其中的关键部分:
public boolean end(Writer writer, String body) {
evaluateParams();
try {
super.end(writer, body, false);
mergeTemplate(writer, buildTemplateName(template, getDefaultTemplate()));
} catch (Exception e) {
throw new StrutsException(e);
}
finally {
popComponentStack();
}
return false;
}
protected Template buildTemplateName(String myTemplate, String myDefaultTemplate) {
String template = myDefaultTemplate;
if (myTemplate != null) {
template = findString(myTemplate);
}
String templateDir = getTemplateDir();
String theme = getTheme();
return new Template(templateDir, theme, template);
}
public String getTemplateDir() {
String templateDir = null;
if (this.templateDir != null) {
templateDir = findString(this.templateDir);//UI标签指定模板的templateDir属性路径
}
// If templateDir is not explicitly given,
// try to find attribute which states the dir set to use
if ((templateDir == null) || (templateDir.equals(""))) {
templateDir = stack.findString("#attr.templateDir");//四个web范围
}
// Default template set
if ((templateDir == null) || (templateDir.equals(""))) {
templateDir = defaultTemplateDir;//默认的模板路径
}
// Defaults to 'template'
if ((templateDir == null) || (templateDir.equals(""))) {
templateDir = "template";
}
return templateDir;
}
public String getTheme() {
String theme = null;
if (this.theme != null) {
theme = findString(this.theme);
}
if ( theme == null || theme.equals("") ) {//对于theme变量这里多了一个级别
Form form = (Form) findAncestor(Form.class);
if (form != null) {
theme = form.getTheme();
}
}
// If theme set is not explicitly given,
// try to find attribute which states the theme set to use
if ((theme == null) || (theme.equals(""))) {
theme = stack.findString("#attr.theme");
}
// Default theme set
if ((theme == null) || (theme.equals(""))) {
theme = defaultUITheme;
}
return theme;
}
上面的代码应该是比较清淅的,不好理解的地方主要在于#attr.theme与#attr.templateDir,关于这一点主要需要搞清楚AttributeMap这个类,因为#attr主AttributeMap的对象它的get方法如下:
public Object get(Object key) {
PageContext pc = getPageContext();
if (pc == null) {
Map request = (Map) context.get("request");
Map session = (Map) context.get("session");
Map application = (Map) context.get("application");
if ((request != null) && (request.get(key) != null)) {
return request.get(key);
} else if ((session != null) && (session.get(key) != null)) {
return session.get(key);
} else if ((application != null) && (application.get(key) != null)) {
return application.get(key);
}
} else {
try{
return pc.findAttribute(key.toString());
}catch (NullPointerException npe){
return null;
}
}
return null;
}
上面的PageContext #findAttribute是按四级范围page,request,session,application依次查找变量的,这就是上面优先级的来历,关于上面的优先级还有一点需要说明,如果在保存变量的时候使用了set标签,并且没有指定范围,在上面的get方法中是否可以获取相应的值呢?这是可以的,分两种情况:如果存在PageContext那当然没得说变量会存在于PageContext中,获取的时候也会从PageContext中获取;第二种情况是不存在PageContext时,set标签在没有指定scope的情况下会将变量放入值栈中,进行再通过#attr.变量名 来访问变量会从requestMap中获取此值,这其中的原因是这样的,一个RequestMap对象封装了一个StrutsRequestWrapper,而StrutsRequestWrapper改变了HttpServletRequest#getAttribute行为,使其在某种程度上可以支持jstl表达式,request除了调用自己的getAttribute方法,还会某些情况下去查询值栈,为了避免死循环需要对表达式有一定的限,其方法源代码如下:
public Object getAttribute(String s) {
if (s != null && s.startsWith("javax.servlet")) {
// don't bother with the standard javax.servlet attributes, we can short-circuit this
// see WW-953 and the forums post linked in that issue for more info
return super.getAttribute(s);
}
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(s);
if (ctx != null) {
if (attribute == null) {
boolean alreadyIn = false;
Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
if (b != null) {
alreadyIn = b.booleanValue();
}
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && s.indexOf("#") == -1) {
try {
// If not found, then try the ValueStack
ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(s);
}
} finally {
ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
}
}
}
}
return attribute;
}
从上面的分析可以看出,RequestMap这个对象在包装一个StrutsRequestWrapper后有了更深一层的含义;另外在值栈中的#attr变量是一个AttributeMap也有其特别之处,它存在变量是将它放在PageContext中,获取值的时候却是按page,request,session,application顺序进行取值,关于#attr与#request两个变量的猫腻通常都会在这两个个特别的地方找到