精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2005-08-29
这种方式是 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"/> 就可以了.. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2005-08-29
是个好办法
如何检测ftl的编码和context-type哪? |
|
返回顶楼 | |
发表时间: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" . |
|
返回顶楼 | |
发表时间:2005-08-29
.....
我是说sitemesh如何知道你的ftl文件是什么编码,什么content-type哪 freemarker内部如何处理不属于你的代码范围了 |
|
返回顶楼 | |
发表时间:2005-08-29
我的习惯是不在Decorator里面引用当前action的变量.
因为decorator文件是为多个页面(未知来源)服务的,里面有啥变量是不确定的,所以这就是不可靠的依赖关系了. 如果我要在decorator里面引用变化的东西,我直接使用ww:action就可以执行一个action,然后就可以调用需要的内容了,不依赖外部其他东西. 如果是include 的文件,也可以考虑直接include一个action... |
|
返回顶楼 | |
发表时间: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的. |
|
返回顶楼 | |
发表时间:2005-08-29
scud 写道 我的习惯是不在Decorator里面引用当前action的变量.
因为decorator文件是为多个页面(未知来源)服务的,里面有啥变量是不确定的,所以这就是不可靠的依赖关系了. 如果我要在decorator里面引用变化的东西,我直接使用ww:action就可以执行一个action,然后就可以调用需要的内容了,不依赖外部其他东西. 如果是include 的文件,也可以考虑直接include一个action... 你说的有道理...这个方案只是让我们拥有了使用action的自由,并没有强迫我们去使用.. ![]() 另一个好处是,我们的web.xml中不再需要定义 sitemesh 的 Filter, 也不再需要定义 FreemarkerDecoratorServlet...每个Action的执行都少了两次处理,提升系统性能是显然的... |
|
返回顶楼 | |
发表时间:2005-08-29
where is deduceLocale? 呵呵 既然要讨论,总的能运行吧
总觉得有点问题哦,呵呵 不过因为没有这么用过,暂时想不出 我最近打算view改用ftl,之前只测试过ftl,所以关注这个话题. 不过一个方案有优点必然就有缺点,优缺点一列,任由选择 ![]() |
|
返回顶楼 | |
发表时间: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最好的"合作"方式,制定自己的整合方案,是有必要的... |
|
返回顶楼 | |
发表时间:2005-08-29
sorry,没注意是继承自FreemarkerResult
我的意思是说完整的文件,不过就一个文件也就无所谓了 ![]() 我太懒了,呵呵 不过好像上面只处理了body哦,看sitemesh的文档,支持: 引用 Sitemesh context attributes base request.getContextPath() title Parsed page title (<title>...<title>) head Parsed page head body Parsed page body page SiteMesh's internal Page object |
|
返回顶楼 | |