论坛首页 Java版 Webwork

讨论一种组合webwork+FreeMarker+sitemesh的方法

浏览 9202 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
时间:2005-08-29
1. 使用 sitemesh 的 Servlet Filter 做页面修饰.

这种方式是 sitemesh 默认的使用方式,我们先来分析一下工作流程.

Action的定义(webwork-default.xml):

[code:1]<result-type name="freemarker" class="com.opensymphony.webwork.views.freemarker.FreemarkerResult" default="true"/>
<action name="viewLogin" class="foo.bar.viewLoginAction">
<result name="success">/login.ftl</result>
</action>[/code:1]

当客户端调用viewLogin,Web容器根据.action后缀,交给webwork的 ServletDispatcher 处理,
这个 viewLogin 会经过 ServletDispatcher->proxy.execute()->Action.execute()->executeResult() 等一系列处理,
因为 viewLogin 的 result-type 是 freemarker, 所以 executeResult() 会调用
com.opensymphony.webwork.views.freemarker.FreemarkerResult 来把 login.ftl 作为Template
写入 response.
FreemarkerResult()在创建FreeMarker Template的时候,会为它创建一个"加强版"的TemplateModel,包含以下对象:
* $stack = OgnlValueStack;
* $webwork = FreemarkerWebWorkUtil, a toolbox providing services like formatting url, accessing the value stack, etc;
* $name-of-property = property retrieved from the value stack.
* $Request = HttpServletRequest;
* $Session = HttpServletResponse;
* $Application = OgnlValueStack.
这个特性是webwork的FreemarkerResult为我们提供的..

这时,webwork的工作基本就结束了,接下来, sitemesh的PageFilter出场了...

PageFilter会根据request找到相应的 decorator, 我们假设它是 main.dec
然后调用
[code:1]RequestDispatcher dispatcher = context.getRequestDispatcher(decorator.getPage());
dispatcher.include(request, response);[/code:1]

因为decorator文件是 .dec 后缀, 而web.xml中映射 .dec 到
com.opensymphony.module.sitemesh.freemarker.FreemarkerDecoratorServlet

所以,此时 FreemarkerDecoratorServlet 接管 .dec 文件, 把 reponse(对应 login.ftl ) Include
到 .dec 的reponse...至此,页面的组合工作就完成了...
不过这次, FreeMarker就没有那么好的运气了, 它的TemplateModel不再是"加强版"的~~
decorator文件中所包含的其他 .ftl 模版, 是不能够使用 $stack, $webwork 等对象的...

总结一下:
粗略的流程是这样的

webwork --> FreemarkerResult --> Sitemesh --> FreeMarker


其中,FreeMarker被调用2次(影响系统性能) ,并且第二次被调用时,其TemplateModel不具备webwork特性(即,非加强版)...

2. 在webwork中使用sitemesh

我们理想中的流程应该是这样的:

webwork --> Sitemesh --> FreemarkerResult

webwork执行Action,执行完毕之后,找到对应的result模版,把这个模版交给sitemesh去修饰,组合成一个新的模版,
再把这个新的模版交给 FreemarkerResult 处理. FreemarkerResult 解析组合后的模版文件并写入response.
所谓"交给sitemesh去修饰",只是调用一下sitemesh的Factory得到其配置文件中所对应的decorator文件,组合模版的工作
还是要我们来做...

这样一来,我们就不再需要 sitemesh 的 Filter, 也不再需要 FreemarkerDecoratorServlet.与第一种方案相比,显然会提升系统性能..

并且更重要的是,这个组合后的模版所对应的TemplateModel是加强版的,decorator中所包含的其他 .ftl 文件,
也同样可以使用 $stack, $webwork 等对象.

此方案的限制条件:
-- decorator文件必须是Freemarker模版,不能用JSP等其他文件...
-- decorator文件的<head></head>部分,必须是写在文件中的,不能是include进来的..
-- 如果 decorator文件的<head></head>部分是include进来的,则源.ftl中的<head></head>中不能含有其他${xxx}

实现方法:

我们需要重载
com.opensymphony.webwork.views.freemarker.FreemarkerResult的doExecute方法,
在它执行 Template.process 之前, 让 sitemesh 为我们组合好新的模版, 然后狸猫换太子, 执行
MergedTemplate.process.

具体代码如下:
[code:1]
package com.simba.webwork.views.freemarker;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import javax.servlet.http.HttpServletRequest;

import com.opensymphony.module.sitemesh.Config;
import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.Factory;
import com.opensymphony.module.sitemesh.HTMLPage;
import com.opensymphony.module.sitemesh.Page;
import com.opensymphony.module.sitemesh.PageParser;
import com.opensymphony.module.sitemesh.filter.TextEncoder;
import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.xwork.ActionInvocation;

import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

/**
*
* @author simba
*
*/
public class SitemeshFreemarkerResult extends FreemarkerResult
{
private static final long serialVersionUID = 6889674369040918834L;
private final static TextEncoder TEXT_ENCODER = new TextEncoder();

public void doExecute(String location, ActionInvocation invocation) throws IOException,
TemplateException
{
this.location = location;
this.invocation = invocation;
this.configuration = getConfiguration();
this.wrapper = getObjectWrapper();

Template template = configuration.getTemplate(location, deduceLocale());
TemplateModel model = createModel();

/*
* 调用mergeTemplate得到组合后的模版...
*/
Template mergedTemplate = mergeTemplate(template, model);

/*
* 执行mergedTemplate的处理
*/

// Give subclasses a chance to hook into preprocessing
if (preTemplateProcess(mergedTemplate, model))
{
try
{
// Process the template
mergedTemplate.process(model, getWriter());
}
finally
{
// Give subclasses a chance to hook into postprocessing
postTemplateProcess(mergedTemplate, model);
}
}
}

/**
* Get decorator from sitemesh's factory according to specific request,
* then get this decorator as a FreeMarker template.
* By replace the "${body}" with "body template" (determined by action result),
* we get the merged template which will handled by FreeMarkerResult.
*
* @param template
* @return
*/
private Template mergeTemplate(Template template, TemplateModel model)
{
HttpServletRequest request = ServletActionContext.getRequest();

//创建sitemesh的Factory实例..
Factory factory = Factory.getInstance(new Config(ServletActionContext.getServletConfig()));

//Determine whether the given path should be excluded from decoration or not.
if(factory.isPathExcluded(extractRequestPath(request)))
return template;

//传入request,sitemesh根据request在decorators.xml中寻找匹配的decorator
Decorator decorator = factory.getDecoratorMapper().getDecorator(request, null);

if (decorator == null)
return template;

try
{
//page是对action传进来的.ftl文件进行解析后得到的.
Page page = parsePage(template, factory);

SimpleHash hash = (SimpleHash) model;

String title, body, head;

if(page==null)
{
title="No Title";
body="No Body";
head="<!-- No head -->";
}
else
{
HTMLPage htmlPage = (HTMLPage)page;

title=htmlPage.getTitle();

StringWriter buffer = new StringWriter();
htmlPage.writeBody(buffer);
body=buffer.toString();

buffer = new StringWriter();
htmlPage.writeHead(buffer);
head=buffer.toString();

hash.put("page",htmlPage);
}

/*
* 这里是为了能让include进来的.ftl模版使用${title},${head}和${base}标签,
* 但是如果include进来的.ftl模版的<head></head>中又使用了FreeMarker的标签,
* 比如${user.name},这个${user.name}就不会被解析了
*/
hash.put("title",title);
hash.put("head",head);
hash.put("base",request.getContextPath());

//将decorator所指向的文件,作为FreeMarker Template载入..
Template decTemplate = configuration.getTemplate(decorator.getPage(), deduceLocale());

String deTemplateString = decTemplate.toString();
deTemplateString = deTemplateString.replace("${body}", body);
deTemplateString = deTemplateString.replace("${title}", title);
deTemplateString = deTemplateString.replace("${head}", head);


return new Template(template.getName(), new StringReader(deTemplateString), configuration);
}
catch (IOException e)
{
e.printStackTrace();
// log me ...
return template;
}
}

private Page parsePage(Template template, Factory factory) throws IOException
{
PageParser pageParser = factory.getPageParser(getContentType()!=null?getContentType():"text/html");
return pageParser.parse(TEXT_ENCODER.encode((template.toString()).getBytes(), template.getEncoding()));
}

private String extractRequestPath(HttpServletRequest request)
{
String servletPath = request.getServletPath();
String pathInfo = request.getPathInfo();
String query = request.getQueryString();
return (servletPath == null ? "" : servletPath) + (pathInfo == null ? "" : pathInfo)
+ (query == null ? "" : ("?" + query));
}

}

[/code:1]


只要在webwork-default.xml文件中指定 result-type为:
[code:1]<result-type name="freemarker" class="foo.bar.SitemeshFreemarkerResult" default="true"/>[/code:1]
就可以了..
   
时间:2005-08-29
goood~!
   
0 请登录后投票
时间:2005-08-29
是个好办法

如何检测ftl的编码和context-type哪?
   
0 请登录后投票
时间:2005-08-29
引用
是个好办法
如何检测ftl的编码和content-type哪?


Template在组合之后,会执行preTemplateProcess(mergedTemplate, model).

preTemplateProcess在FreemarkerResult中的实现是:
[code:1]
protected boolean preTemplateProcess(Template template, TemplateModel model) throws IOException {
Object attrContentType = template.getCustomAttribute("content_type");

if (attrContentType != null) {
ServletActionContext.getResponse().setContentType(attrContentType.toString());
} else {
String contentType = getContentType();

if (contentType == null) {
contentType = "text/html";
}

String encoding = template.getEncoding();

if (encoding != null) {
contentType = contentType + "; charset=" + encoding;
}

ServletActionContext.getResponse().setContentType(contentType);
}

return true;
}[/code:1]

所以,ftl的编码和content-type会在mergedTemplate.process()执行之前被写入response

----------------------------------------------------------------------------------------------

另外,在实际使用中,最好重载
com.opensymphony.webwork.views.freemarker.FreemarkerResult的preTemplateProcess(),加入以下代码,

[code:1]protected boolean preTemplateProcess(Template template, TemplateModel model) throws IOException
{
boolean ret = super.preTemplateProcess(template, model);

SimpleHash hash = (SimpleHash) model;
Object session = hash.get("Session");
if(session == null)
{
hash.put("Session","");
}

return ret;
}[/code:1]

因为有些时候,比如调用logout.action, 会把session清掉.
这时,如果 .ftl 文件中有:
<#if Session["LOGIN_USER"]?exists>
这样的语句存在,则因为session不在TemplateModel中,而导致FreeMarker报错,说"Expression Session Not Defined" .
   
0 请登录后投票
时间:2005-08-29
.....
我是说sitemesh如何知道你的ftl文件是什么编码,什么content-type哪

freemarker内部如何处理不属于你的代码范围了
   
0 请登录后投票
时间:2005-08-29
我的习惯是不在Decorator里面引用当前action的变量.

因为decorator文件是为多个页面(未知来源)服务的,里面有啥变量是不确定的,所以这就是不可靠的依赖关系了.

如果我要在decorator里面引用变化的东西,我直接使用ww:action就可以执行一个action,然后就可以调用需要的内容了,不依赖外部其他东西.

如果是include 的文件,也可以考虑直接include一个action...
   
0 请登录后投票
时间:2005-08-29
scud 写道
.....
我是说sitemesh如何知道你的ftl文件是什么编码,什么content-type哪
freemarker内部如何处理不属于你的代码范围了

呵呵,明白你的意思了..

freemarker.properties中,可以设置编码
default_encoding=UTF8

与sitemesh没有关系.因为我们只是从sitemesh那里拿到了我们想要的decorator的path.然后,是FreeMarker去读取这个decorator.
Template decTemplate = configuration.getTemplate(decorator.getPage(), deduceLocale());
这个configuration是freemarker.template.Configuration.而不是sitemesh的.
   
0 请登录后投票
时间:2005-08-29
scud 写道
我的习惯是不在Decorator里面引用当前action的变量.

因为decorator文件是为多个页面(未知来源)服务的,里面有啥变量是不确定的,所以这就是不可靠的依赖关系了.

如果我要在decorator里面引用变化的东西,我直接使用ww:action就可以执行一个action,然后就可以调用需要的内容了,不依赖外部其他东西.

如果是include 的文件,也可以考虑直接include一个action...


你说的有道理...这个方案只是让我们拥有了使用action的自由,并没有强迫我们去使用..
另一个好处是,我们的web.xml中不再需要定义 sitemesh 的 Filter, 也不再需要定义 FreemarkerDecoratorServlet...每个Action的执行都少了两次处理,提升系统性能是显然的...
   
0 请登录后投票
时间:2005-08-29
where is deduceLocale? 呵呵 既然要讨论,总的能运行吧

总觉得有点问题哦,呵呵 不过因为没有这么用过,暂时想不出

我最近打算view改用ftl,之前只测试过ftl,所以关注这个话题.

不过一个方案有优点必然就有缺点,优缺点一列,任由选择
   
0 请登录后投票
时间:2005-08-29
scud 写道
where is deduceLocale? 呵呵 既然要讨论,总的能运行吧

总觉得有点问题哦,呵呵 不过因为没有这么用过,暂时想不出


前面说过了,FreeMarker根据freemarker.properties的设定来判断编码,
deduceLocale就是从configuration中读取编码的设定值..

in com.opensymphony.webwork.views.freemarker.FreemarkerResult.
[code:1] /**
* Returns the locale used for the
* {@link Configuration#getTemplate(String, Locale)} call.
* The base implementation simply returns the locale setting of the
* configuration. Override this method to provide different behaviour,
*/
protected Locale deduceLocale() {
return configuration.getLocale();
}[/code:1]

[code:1]总得能运行吧[/code:1]
呵呵,如果不能运行,如果运行有问题,偶怎么敢拿出来丢人啊~~
不过如果能找出问题,倒是算有收获哦~~

其实,webwork也好,sitemesh也好,freemarker也好,他们都不是为了另一个framework而存在的...所以它们必须能做"万金油",就好比sitemesh用Servlet Filter来完成自己的工作(而我们不需要它帮我们提交request,只要拿到decorator就好)..而我们在考虑整合这些framework的时候,选择了freemarker就意味着不会去用velocity,找出这些framework最好的"合作"方式,制定自己的整合方案,是有必要的...
   
0 请登录后投票
论坛首页 Java版 Webwork

跳转论坛:
JavaEye推荐