论坛首页 Java企业应用论坛

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

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

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

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

<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>


当客户端调用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
然后调用
RequestDispatcher dispatcher = context.getRequestDispatcher(decorator.getPage(););;
dispatcher.include(request, response);;


因为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.

具体代码如下:
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););;
	}
	
}




只要在webwork-default.xml文件中指定 result-type为:
<result-type name="freemarker" class="foo.bar.SitemeshFreemarkerResult" default="true"/>

就可以了..
   发表时间:2005-08-29  
是个好办法

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


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

preTemplateProcess在FreemarkerResult中的实现是:
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;
    }


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

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

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

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;
}


因为有些时候,比如调用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.
   /**
     * 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();;
    }


总得能运行吧

呵呵,如果不能运行,如果运行有问题,偶怎么敢拿出来丢人啊~~
不过如果能找出问题,倒是算有收获哦~~

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

我的意思是说完整的文件,不过就一个文件也就无所谓了

我太懒了,呵呵

不过好像上面只处理了body哦,看sitemesh的文档,支持:

引用

Sitemesh context attributes

base request.getContextPath()
title Parsed page title (&lt;title&gt;...&lt;title&gt;)
head Parsed page head
body Parsed page body
page SiteMesh's internal Page object


0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics