SiteMesh简介
sitemesh应用Decorator模式,用filter截取request和response,把页面组件head,content,banner结合为一个完整的视图。通常我们都是用include标签在每个jsp页面中来不断的包含各种header, stylesheet, scripts and footer,现在,在sitemesh的帮助下,我们可以开心的删掉他们了。如下图,你想轻松的达到复合视图模式。
应用siteMesh方法:
一、在WEB-INF/web.xml中copy以下filter的定义:
<?xml version="1.0" encoding="GBK"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
二、copy所需sitemesh-2.3.jar到WEB-INF\lib下。
三、 建立WEB-INF/decorators.xml描述各装饰器页面。
<decorators defaultdir="/decorators">
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
</decorators>
上面配置文件指定了装饰器页面所在的路径(/decorators),并指定了一个名为main的装饰器,该装饰器默认装饰web应用根路径下的所有页面。
四、 建立装饰器页面 /decorators/main.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator"%> <html>
<head>
<title><decorator:title default="装饰器页面..." /></title>
<decorator:head />
</head>
<body>
sitemesh的例子<hr>
<decorator:body />
<hr>chen56@msn.com
</body>
</html>
五、建立一个的被装饰页面 /index.jsp(内容页面)
<%@ page contentType="text/html; charset=GBK"%>
<html>
<head>
<title>Agent Test</title>
</head>
<body>
<p>本页只有一句,就是本句.</p>
</body>
</html>
最后访问index.jsp,将生成如下页面:
而且,所有的页面也会如同index.jsp一样,被sitemesh的filter使用装饰模式修改成如上图般模样,却不用再使用include标签。
装饰器 decorator概念
sitemesh通过filter截取request和response,并给原始的页面加入一定的装饰(可能为header,footer...),然后把结果返回给客户端,并且被装饰的原始页面并不知道sitemesh的装饰,这也就达到了脱耦的目的。
除了要copy到WEB-INF/lib中的sitemesh.jar外,还有2个文件要建立到WEB-INF/:
_ sitemesh.xml (可选)
_ decorators.xml
sitemesh.xml 可以设置2种信息:
Page Parsers :负责读取stream的数据到一个Page对象中以被SiteMesh解析和操作。(不太常用,默认即可)
Decorator Mappers : 不同的装饰器种类,2种比较有用都列在下面。一种通用的mapper,可以指定装饰器的配置文件名,另一种可打印的装饰器,可以允许你当用http://localhost/aaa/a.html?printable=true方式访问时给出原始页面以供打印(免得把header,footer等的花哨的图片也搭上)
范例:
<sitemesh>
<page-parsers>
<parser default="true" class="com.opensymphony.module.sitemesh.parser.DefaultPageParser" />
<parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
<parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
</page-parsers>
<decorator-mappers>
<mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="/WEB-INF/decorators.xml" />
</mapper>
<mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
<param name="decorator" value="printable" />
<param name="parameter.name" value="printable" />
<param name="parameter.value" value="true" />
</mapper>
</decorator-mappers>
</sitemesh>
decorators.xml :定义构成复合视图的所有页面构件的描述(主要结构页面,header,footer...),如下例:
<decorators defaultdir="/decorators">
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
<decorator name="printable" page="printable.jsp" role="customer" webapp="aaa" />
</decorators>
_ defaultdir: 包含装饰器页面的目录
_ page : 页面文件名
_ name : 别名
_ role : 角色,用于安全
_ webapp : 可以另外指定此文件存放目录
_ Patterns : 匹配的路径,可以用*,那些被访问的页面需要被装饰。
最重要的是写出装饰器本身(也就是那些要复用页面,和结构页面)。
其实,重要的工作就是制作装饰器页面本身(也就是包含结构和规则的页面),然后把他们描述到decorators.xml中。
让我们来先看一看最简单的用法:其实最常用也最简单的用法就是我们的hello例子,
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
<html>
<head>
<title><decorator:title default="装饰器页面..." /></title>
<decorator:head />
</head>
<body>
sitemesh的例子<hr>
<decorator:body />
<hr>
</body>
</html>
我们在装饰器页面只用了2个标签:
<decorator:title default="装饰器页面..." /> : 把请求的原始页面的title内容插入到<title></title>中间。
<decorator:body /> : 把请求的原始页面的body内的全部内容插入到相应位置。
然后我们在decorator.xml中加入以下描述即可:
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
这样,请求的所有页面都会被重新处理,并按照main.jsp的格式重新展现在你面前。
FreeMarker简介
FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写
FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序
虽然FreeMarker具有一些编程的能力,但通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据(如下图)
FreeMarker不是一个Web应用框架,而适合作为Web应用框架一个组件
FreeMarker与容器无关,因为它并不知道HTTP或Servlet;FreeMarker同样可以应用于非Web应用程序环境
FreeMarker更适合作为Model2框架(如Struts)的视图组件,你也可以在模板中使用JSP标记库
FreeMarker是免费的
1、通用目标
能够生成各种文本:HTML、XML、RTF、Java源代码等等
易于嵌入到你的产品中:轻量级;不需要Servlet环境
插件式模板载入器:可以从任何源载入模板,如本地文件、数据库等等
你可以按你所需生成文本:保存到本地文件;作为Email发送;从Web应用程序发送它返回给Web浏览器
2、强大的模板语言
所有常用的指令:include、if/elseif/else、循环结构
在模板中创建和改变变量
几乎在任何地方都可以使用复杂表达式来指定值
命名的宏,可以具有位置参数和嵌套内容
名字空间有助于建立和维护可重用的宏库,或者将一个大工程分成模块,而不必担心名字冲突
输出转换块:在嵌套模板片段生成输出时,转换HTML转义、压缩、语法高亮等等;你可以定义自己的转换
3、通用数据模型
FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量方式在模板中显示
你可以使用抽象(接口)方式表示对象(JavaBean、XML文档、SQL查询结果集等等),告诉模板开发者使用方法,使其不受技术细节的打扰
4、为Web准备
在模板语言中内建处理典型Web相关任务(如HTML转义)的结构
能够集成到Model2 Web应用框架中作为JSP的替代
支持JSP标记库
为MVC模式设计:分离可视化设计和应用程序逻辑;分离页面设计员和程序员
5、智能的国际化和本地化
字符集智能化(内部使用UNICODE)
数字格式本地化敏感
日期和时间格式本地化敏感
非US字符集可以用作标识(如变量名)
多种不同语言的相同模板
6、强大的XML处理能力
<#recurse> 和<#visit>指令(2.3版本)用于递归遍历XML树
在模板中清楚和直觉的访问XML对象模型
(1)模板 + 数据模型 = 输出
FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念
他们是分工劳动的:设计者专注于表示——创建HTML文件、图片、Web页面的其它可视化方面;程序员创建系统,生成设计页面要显示的数据
经常会遇到的问题是:在Web页面(或其它类型的文档)中显示的信息在设计页面时是无效的,是基于动态数据的
在这里,你可以在HTML(或其它要输出的文本)中加入一些特定指令,FreeMarker会在输出页面给最终用户时,用适当的数据替代这些代码
下面是一个例子:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
这个例子是在简单的HTML中加入了一些由${…}包围的特定代码,这些特定代码是FreeMarker的指令,而包含FreeMarker的指令的文件就称为模板(Template)
至于user、latestProduct.url和latestProduct.name来自于数据模型(data model)
数据模型由程序员编程来创建,向模板提供变化的信息,这些信息来自于数据库、文件,甚至于在程序中直接生成
模板设计者不关心数据从那儿来,只知道使用已经建立的数据模型
下面是一个可能的数据模型:
(root)
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
数据模型类似于计算机的文件系统,latestProduct可以看作是目录,而user、url和name看作是文件,url和name文件位于latestProduct目录中(这只是一个比喻,实际并不存在)
当FreeMarker将上面的数据模型合并到模板中,就创建了下面的输出:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome Big Joe!</h1>
<p>Our latest product:
<a href="products/greenmouse.html">green mouse</a>!
</body>
</html>
(2)数据模型
典型的数据模型是树型结构,可以任意复杂和深层次,如下面的例子:
(root)
|
+- animals
| |
| +- mouse
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- elephant
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- python
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- test = "It is a test"
|
+- whatnot
|
+- because = "don't know"
类似于目录的变量称为hashes,包含保存下级变量的唯一的查询名字
类似于文件的变量称为scalars,保存单值
scalars保存的值有两种类型:字符串(用引号括起,可以是单引号或双引号)和数字(不要用引号将数字括起,这会作为字符串处理)
对scalars的访问从root开始,各部分用“.”分隔,如animals.mouse.price
另外一种变量是sequences,和hashes类似,只是不使用变量名字,而使用数字索引,如下面的例子:
(root)
|
+- animals
| |
| +- (1st)
| | |
| | +- name = "mouse"
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- (2nd)
| | |
| | +- name = "elephant"
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- (3rd)
| |
| +- name = "python"
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- whatnot
|
+- fruits
|
+- (1st) = "orange"
|
+- (2nd) = "banana"
这种对scalars的访问使用索引,如animals[0].name
(3)模板
在FreeMarker模板中可以包括下面三种特定部分:
1:${…}:称为interpolations,FreeMarker会在输出时用实际值进行替代
2:FTL标记(FreeMarker模板语言标记):类似于HTML标记,为了与HTML标记区分,用#开始(有些以@开始,在后面叙述)
3:注释:包含在<#--和-->(而不是<!--和-->)之间
下面是一些使用指令的例子:
1: if指令
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
<#else>
Pythons are not cheaper than elephants today.
</#if>
2:list指令
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr><td>${being.name}<td>${being.price} Euros
</#list>
</table>
输出为:
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<tr><td>mouse<td>50 Euros
<tr><td>elephant<td>5000 Euros
<tr><td>python<td>4999 Euros
</table>
3: include指令
<html>
<head>
<title>Test page</title>
</head>
<body>
<h1>Test page</h1>
<p>Blah blah...
<#include "/copyright_footer.html">
</body>
</html>
4:一起使用指令
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr>
<td>
<#if being.size = "large"><b></#if>
${being.name}
<#if being.size = "large"></b></#if>
<td>${being.price} Euros
</#list>
</table>
4、杂项
(1)用户定义指令
a: 宏和变换器变量是两种不同类型的用户定义指令,它们之间的区别是宏是在模板中使用macro指令定义,而变换器是在模板外由程序定义,这里只介绍宏
b: 基本用法
宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量,下面是一个例子:
<#macro greet>
<font size="+2">Hello Joe!</font>
</#macro>
作为用户定义指令使用宏变量时,使用@替代FTL标记中的#
<@greet></@greet>
如果没有体内容,也可以使用:
<@greet/>
参数
在macro指令中可以在宏变量之后定义参数,如:
<#macro greet person>
<font size="+2">Hello ${person}!</font>
</#macro>
可以这样使用这个宏变量:
<@greet person="Fred"/> and <@greet person="Batman"/>
输出结果是:
<font size="+2">Hello Fred!</font>
and <font size="+2">Hello Batman!</font>
宏的参数是FTL表达式,所以下面的代码具有不同的意思:
<@greet person=Fred/>
这意味着将Fred变量的值传给person参数,该值不仅是字符串,还可以是其它类型,甚至是复杂的表达式
宏可以有多参数,下面是一个例子:
<#macro greet person color>
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
可以这样使用该宏变量:
<@greet person="Fred" color="black"/>
其中参数的次序是无关的,因此下面是等价的:
<@greet color="black" person="Fred"/>
只能使用在macro指令中定义的参数,并且对所有参数赋值,所以下面的代码是错误的:
<@greet person="Fred" color="black" background="green"/>
<@greet person="Fred"/>
可以在定义参数时指定缺省值,如:
<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
这样<@greet person="Fred"/>就正确了
宏的参数是局部变量,只能在宏定义中有效
c : 嵌套内容
用户定义指令可以有嵌套内容,使用<#nested>指令执行指令开始和结束标记之间的模板片断
例子:
<#macro border>
<table border=4 cellspacing=0 cellpadding=4>
<tr><td>
<#nested>
</tr></td>
</table>
</#macro>
这样使用该宏变量:
<@border>The bordered text</@border>
输出结果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</tr></td></table>
<#nested>指令可以被多次调用,例如:
<#macro do_thrice>
<#nested>
<#nested>
<#nested>
</#macro>
<@do_thrice>
Anything.
</@do_thrice>
输出结果:
Anything.
Anything.
Anything.
嵌套内容可以是有效的FTL,下面是一个有些复杂的例子:
<@border>
<ul>
<@do_thrice>
<li><@greet person="Joe"/>
</@do_thrice>
</ul>
</@border>
输出结果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<ul>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
</ul>
</tr></td></table>
宏定义中的局部变量对嵌套内容是不可见的,例如:
<#macro repeat count>
<#local y = "test">
<#list 1..count as x>
${y} ${count}/${x}: <#nested>
</#list>
</#macro>
<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>
输出结果:
test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?
在宏定义中使用循环变量
用户定义指令可以有循环变量,通常用于重复嵌套内容,基本用法是:
作为nested指令的参数传递循环变量的实际值,而在调用用户定义指令时,在<@…>开始标记的参数后面指定循环变量的名字
例子:
<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count>
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
输出结果:
1. 0.5
2. 1
3. 1.5
4. 2 Last!
Ø 指定的循环变量的数目和用户定义指令开始标记指定的不同不会有问题
n 调用时少指定循环变量,则多指定的值不可见
n 调用时多指定循环变量,多余的循环变量不会被创建
(2)在模板中定义变量
在模板中定义的变量有三种类型:
plain变量:可以在模板的任何地方访问,包括使用include指令插入的模板,使用assign指令创建和替换
局部变量:在宏定义体中有效,使用local指令创建和替换
循环变量:只能存在于指令的嵌套内容,由指令(如list)自动创建;宏的参数是局部变量,而不是循环变量
局部变量隐藏(而不是覆盖)同名的plain变量;循环变量隐藏同名的局部变量和plain变量,下面是一个例子:
<#assign x = "plain">
1. ${x} <#-- we see the plain var. here -->
<@test/>
6. ${x} <#-- the value of plain var. was not changed -->
<#list ["loop"] as x>
7. ${x} <#-- now the loop var. hides the plain var. -->
<#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
8. ${x} <#-- it still hides the plain var. -->
</#list>
9. ${x} <#-- the new value of plain var. -->
<#macro test>
2. ${x} <#-- we still see the plain var. here -->
<#local x = "local">
3. ${x} <#-- now the local var. hides it -->
<#list ["loop"] as x>
4. ${x} <#-- now the loop var. hides the local var. -->
</#list>
5. ${x} <#-- now we see the local var. again -->
</#macro>
输出结果:
1. plain
2. plain
3. local
4. loop
5. local
6. plain
7. loop
8. loop
9. plain2
内部循环变量隐藏同名的外部循环变量,如:
<#list ["loop 1"] as x>
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>
输出结果:
loop 1
loop 2
loop 3
loop 2
loop 1
模板中的变量会隐藏(而不是覆盖)数据模型中同名变量,如果需要访问数据模型中的同名变量,使用特殊变量global
下面的例子假设数据模型中的user的值是Big Joe:
<#assign user = "Joe Hider">
${user} <#-- prints: Joe Hider -->
${.globals.user} <#-- prints: Big Joe -->
(3)名字空间
通常情况,只使用一个名字空间,称为主名字空间
为了创建可重用的宏、变换器或其它变量的集合(通常称库),必须使用多名字空间,其目的是防止同名冲突
创建库
下面是一个创建库的例子(假设保存在lib/my_test.ftl中):
<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>
<#assign mail = "jsmith@acme.com">
使用import指令导入库到模板中,Freemarker会为导入的库创建新的名字空间,并可以通过import指令中指定的散列变量访问库中的变量:
<#import "/lib/my_test.ftl" as my>
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}
输出结果:
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
<br>Email: jsmith@acme.com</p>
jsmith@acme.com
fred@acme.com
可以看到例子中使用的两个同名变量并没有冲突,因为它们位于不同的名字空间
可以使用assign指令在导入的名字空间中创建或替代变量,下面是一个例子:
<#import "/lib/my_test.ftl" as my>
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
输出结果:
jsmith@acme.com
jsmith@other.com
数据模型中的变量任何地方都可见,也包括不同的名字空间,下面是修改的库:
<#macro copyright date>
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">
假设数据模型中的user变量的值是Fred,则下面的代码:
<#import "/lib/my_test.ftl" as my>
<@my.copyright date="1999-2002"/>
${my.mail}
输出结果:
<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
Fred@acme.com
Sruts2.X对siteMesh和FreeMarker的支持
首先struts2.X的默认模板语言是freemarker
struts2.0将SiteMesh的拦截器进行了扩展增加了对Velocity和Freemarker模板文件的支持,
org.apache.struts2.sitemesh.FreeMarkerPageFilter 应用这个 Filter拦截对freeMarker文件的请求,对服务器响应页面进行装饰,返回给客户统一的页面布局
Struts2.0将com.opensymphony.module.sitemesh.filter.PageFilter 扩展为了 org.apache.struts2.sitemesh.FreeMarkerPageFilter,以后直接使用后者就可以了
如要是使用 Velocity模板的话将web.xml文件中的 siteMesh的实现类替换为: org.apache.struts2.sitemesh.VelocityPageFilter即可.
可以在SiteMesh模板中使用struts标签,在模板中使用struts标签必须增加一个叫 ActionContextCleanUp 的过滤器,
如果在Freemarker模板文件中使用jsp标签(siteMesh标签是Jsp标签的一种)则必须在 web.xml 文件中启动JspSupportServlet,即在web.xml文件中加入如下配置片段:
<servlet>
<servlet-name>JspSupportServlet</servlet-name>
<servlet-class>org.apache.struts2.views.JspSupportServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
最后的web.xml文件中的配置如下:
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>org.apache.struts2.sitemesh.FreeMarkerPageFilter</filter-class>
</filter>
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>JspSupportServlet</servlet-name>
<servlet-class>org.apache.struts2.views.JspSupportServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
freeMarker 的模板中不能直接使用JSP标签,所以不能FreeMarker模板中使用JSP的标签<decorator:head/>等,但是FreeMarker提供了更简单的方式来输出被装饰页面的内容,
下边列出了FreeMarker提供的更简单的输出被装饰页面的方式:
${title}:输出被装饰页面的title部分
${head} :输出被装饰页面的Head部分
${body} :输出被装饰页面的body部分
sitemesh应用Decorator模式,用filter截取request和response,把页面组件head,content,banner结合为一个完整的视图。通常我们都是用include标签在每个jsp页面中来不断的包含各种header, stylesheet, scripts and footer,现在,在sitemesh的帮助下,我们可以开心的删掉他们了。如下图,你想轻松的达到复合视图模式。
应用siteMesh方法:
一、在WEB-INF/web.xml中copy以下filter的定义:
<?xml version="1.0" encoding="GBK"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
二、copy所需sitemesh-2.3.jar到WEB-INF\lib下。
三、 建立WEB-INF/decorators.xml描述各装饰器页面。
<decorators defaultdir="/decorators">
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
</decorators>
上面配置文件指定了装饰器页面所在的路径(/decorators),并指定了一个名为main的装饰器,该装饰器默认装饰web应用根路径下的所有页面。
四、 建立装饰器页面 /decorators/main.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator"%> <html>
<head>
<title><decorator:title default="装饰器页面..." /></title>
<decorator:head />
</head>
<body>
sitemesh的例子<hr>
<decorator:body />
<hr>chen56@msn.com
</body>
</html>
五、建立一个的被装饰页面 /index.jsp(内容页面)
<%@ page contentType="text/html; charset=GBK"%>
<html>
<head>
<title>Agent Test</title>
</head>
<body>
<p>本页只有一句,就是本句.</p>
</body>
</html>
最后访问index.jsp,将生成如下页面:
而且,所有的页面也会如同index.jsp一样,被sitemesh的filter使用装饰模式修改成如上图般模样,却不用再使用include标签。
装饰器 decorator概念
sitemesh通过filter截取request和response,并给原始的页面加入一定的装饰(可能为header,footer...),然后把结果返回给客户端,并且被装饰的原始页面并不知道sitemesh的装饰,这也就达到了脱耦的目的。
除了要copy到WEB-INF/lib中的sitemesh.jar外,还有2个文件要建立到WEB-INF/:
_ sitemesh.xml (可选)
_ decorators.xml
sitemesh.xml 可以设置2种信息:
Page Parsers :负责读取stream的数据到一个Page对象中以被SiteMesh解析和操作。(不太常用,默认即可)
Decorator Mappers : 不同的装饰器种类,2种比较有用都列在下面。一种通用的mapper,可以指定装饰器的配置文件名,另一种可打印的装饰器,可以允许你当用http://localhost/aaa/a.html?printable=true方式访问时给出原始页面以供打印(免得把header,footer等的花哨的图片也搭上)
范例:
<sitemesh>
<page-parsers>
<parser default="true" class="com.opensymphony.module.sitemesh.parser.DefaultPageParser" />
<parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
<parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
</page-parsers>
<decorator-mappers>
<mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="/WEB-INF/decorators.xml" />
</mapper>
<mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
<param name="decorator" value="printable" />
<param name="parameter.name" value="printable" />
<param name="parameter.value" value="true" />
</mapper>
</decorator-mappers>
</sitemesh>
decorators.xml :定义构成复合视图的所有页面构件的描述(主要结构页面,header,footer...),如下例:
<decorators defaultdir="/decorators">
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
<decorator name="printable" page="printable.jsp" role="customer" webapp="aaa" />
</decorators>
_ defaultdir: 包含装饰器页面的目录
_ page : 页面文件名
_ name : 别名
_ role : 角色,用于安全
_ webapp : 可以另外指定此文件存放目录
_ Patterns : 匹配的路径,可以用*,那些被访问的页面需要被装饰。
最重要的是写出装饰器本身(也就是那些要复用页面,和结构页面)。
其实,重要的工作就是制作装饰器页面本身(也就是包含结构和规则的页面),然后把他们描述到decorators.xml中。
让我们来先看一看最简单的用法:其实最常用也最简单的用法就是我们的hello例子,
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
<html>
<head>
<title><decorator:title default="装饰器页面..." /></title>
<decorator:head />
</head>
<body>
sitemesh的例子<hr>
<decorator:body />
<hr>
</body>
</html>
我们在装饰器页面只用了2个标签:
<decorator:title default="装饰器页面..." /> : 把请求的原始页面的title内容插入到<title></title>中间。
<decorator:body /> : 把请求的原始页面的body内的全部内容插入到相应位置。
然后我们在decorator.xml中加入以下描述即可:
<decorator name="main" page="main.jsp">
<pattern>*</pattern>
</decorator>
这样,请求的所有页面都会被重新处理,并按照main.jsp的格式重新展现在你面前。
FreeMarker简介
FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写
FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序
虽然FreeMarker具有一些编程的能力,但通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据(如下图)
FreeMarker不是一个Web应用框架,而适合作为Web应用框架一个组件
FreeMarker与容器无关,因为它并不知道HTTP或Servlet;FreeMarker同样可以应用于非Web应用程序环境
FreeMarker更适合作为Model2框架(如Struts)的视图组件,你也可以在模板中使用JSP标记库
FreeMarker是免费的
1、通用目标
能够生成各种文本:HTML、XML、RTF、Java源代码等等
易于嵌入到你的产品中:轻量级;不需要Servlet环境
插件式模板载入器:可以从任何源载入模板,如本地文件、数据库等等
你可以按你所需生成文本:保存到本地文件;作为Email发送;从Web应用程序发送它返回给Web浏览器
2、强大的模板语言
所有常用的指令:include、if/elseif/else、循环结构
在模板中创建和改变变量
几乎在任何地方都可以使用复杂表达式来指定值
命名的宏,可以具有位置参数和嵌套内容
名字空间有助于建立和维护可重用的宏库,或者将一个大工程分成模块,而不必担心名字冲突
输出转换块:在嵌套模板片段生成输出时,转换HTML转义、压缩、语法高亮等等;你可以定义自己的转换
3、通用数据模型
FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量方式在模板中显示
你可以使用抽象(接口)方式表示对象(JavaBean、XML文档、SQL查询结果集等等),告诉模板开发者使用方法,使其不受技术细节的打扰
4、为Web准备
在模板语言中内建处理典型Web相关任务(如HTML转义)的结构
能够集成到Model2 Web应用框架中作为JSP的替代
支持JSP标记库
为MVC模式设计:分离可视化设计和应用程序逻辑;分离页面设计员和程序员
5、智能的国际化和本地化
字符集智能化(内部使用UNICODE)
数字格式本地化敏感
日期和时间格式本地化敏感
非US字符集可以用作标识(如变量名)
多种不同语言的相同模板
6、强大的XML处理能力
<#recurse> 和<#visit>指令(2.3版本)用于递归遍历XML树
在模板中清楚和直觉的访问XML对象模型
(1)模板 + 数据模型 = 输出
FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念
他们是分工劳动的:设计者专注于表示——创建HTML文件、图片、Web页面的其它可视化方面;程序员创建系统,生成设计页面要显示的数据
经常会遇到的问题是:在Web页面(或其它类型的文档)中显示的信息在设计页面时是无效的,是基于动态数据的
在这里,你可以在HTML(或其它要输出的文本)中加入一些特定指令,FreeMarker会在输出页面给最终用户时,用适当的数据替代这些代码
下面是一个例子:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
这个例子是在简单的HTML中加入了一些由${…}包围的特定代码,这些特定代码是FreeMarker的指令,而包含FreeMarker的指令的文件就称为模板(Template)
至于user、latestProduct.url和latestProduct.name来自于数据模型(data model)
数据模型由程序员编程来创建,向模板提供变化的信息,这些信息来自于数据库、文件,甚至于在程序中直接生成
模板设计者不关心数据从那儿来,只知道使用已经建立的数据模型
下面是一个可能的数据模型:
(root)
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
数据模型类似于计算机的文件系统,latestProduct可以看作是目录,而user、url和name看作是文件,url和name文件位于latestProduct目录中(这只是一个比喻,实际并不存在)
当FreeMarker将上面的数据模型合并到模板中,就创建了下面的输出:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome Big Joe!</h1>
<p>Our latest product:
<a href="products/greenmouse.html">green mouse</a>!
</body>
</html>
(2)数据模型
典型的数据模型是树型结构,可以任意复杂和深层次,如下面的例子:
(root)
|
+- animals
| |
| +- mouse
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- elephant
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- python
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- test = "It is a test"
|
+- whatnot
|
+- because = "don't know"
类似于目录的变量称为hashes,包含保存下级变量的唯一的查询名字
类似于文件的变量称为scalars,保存单值
scalars保存的值有两种类型:字符串(用引号括起,可以是单引号或双引号)和数字(不要用引号将数字括起,这会作为字符串处理)
对scalars的访问从root开始,各部分用“.”分隔,如animals.mouse.price
另外一种变量是sequences,和hashes类似,只是不使用变量名字,而使用数字索引,如下面的例子:
(root)
|
+- animals
| |
| +- (1st)
| | |
| | +- name = "mouse"
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- (2nd)
| | |
| | +- name = "elephant"
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- (3rd)
| |
| +- name = "python"
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- whatnot
|
+- fruits
|
+- (1st) = "orange"
|
+- (2nd) = "banana"
这种对scalars的访问使用索引,如animals[0].name
(3)模板
在FreeMarker模板中可以包括下面三种特定部分:
1:${…}:称为interpolations,FreeMarker会在输出时用实际值进行替代
2:FTL标记(FreeMarker模板语言标记):类似于HTML标记,为了与HTML标记区分,用#开始(有些以@开始,在后面叙述)
3:注释:包含在<#--和-->(而不是<!--和-->)之间
下面是一些使用指令的例子:
1: if指令
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
<#else>
Pythons are not cheaper than elephants today.
</#if>
2:list指令
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr><td>${being.name}<td>${being.price} Euros
</#list>
</table>
输出为:
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<tr><td>mouse<td>50 Euros
<tr><td>elephant<td>5000 Euros
<tr><td>python<td>4999 Euros
</table>
3: include指令
<html>
<head>
<title>Test page</title>
</head>
<body>
<h1>Test page</h1>
<p>Blah blah...
<#include "/copyright_footer.html">
</body>
</html>
4:一起使用指令
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr>
<td>
<#if being.size = "large"><b></#if>
${being.name}
<#if being.size = "large"></b></#if>
<td>${being.price} Euros
</#list>
</table>
4、杂项
(1)用户定义指令
a: 宏和变换器变量是两种不同类型的用户定义指令,它们之间的区别是宏是在模板中使用macro指令定义,而变换器是在模板外由程序定义,这里只介绍宏
b: 基本用法
宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量,下面是一个例子:
<#macro greet>
<font size="+2">Hello Joe!</font>
</#macro>
作为用户定义指令使用宏变量时,使用@替代FTL标记中的#
<@greet></@greet>
如果没有体内容,也可以使用:
<@greet/>
参数
在macro指令中可以在宏变量之后定义参数,如:
<#macro greet person>
<font size="+2">Hello ${person}!</font>
</#macro>
可以这样使用这个宏变量:
<@greet person="Fred"/> and <@greet person="Batman"/>
输出结果是:
<font size="+2">Hello Fred!</font>
and <font size="+2">Hello Batman!</font>
宏的参数是FTL表达式,所以下面的代码具有不同的意思:
<@greet person=Fred/>
这意味着将Fred变量的值传给person参数,该值不仅是字符串,还可以是其它类型,甚至是复杂的表达式
宏可以有多参数,下面是一个例子:
<#macro greet person color>
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
可以这样使用该宏变量:
<@greet person="Fred" color="black"/>
其中参数的次序是无关的,因此下面是等价的:
<@greet color="black" person="Fred"/>
只能使用在macro指令中定义的参数,并且对所有参数赋值,所以下面的代码是错误的:
<@greet person="Fred" color="black" background="green"/>
<@greet person="Fred"/>
可以在定义参数时指定缺省值,如:
<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
这样<@greet person="Fred"/>就正确了
宏的参数是局部变量,只能在宏定义中有效
c : 嵌套内容
用户定义指令可以有嵌套内容,使用<#nested>指令执行指令开始和结束标记之间的模板片断
例子:
<#macro border>
<table border=4 cellspacing=0 cellpadding=4>
<tr><td>
<#nested>
</tr></td>
</table>
</#macro>
这样使用该宏变量:
<@border>The bordered text</@border>
输出结果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</tr></td></table>
<#nested>指令可以被多次调用,例如:
<#macro do_thrice>
<#nested>
<#nested>
<#nested>
</#macro>
<@do_thrice>
Anything.
</@do_thrice>
输出结果:
Anything.
Anything.
Anything.
嵌套内容可以是有效的FTL,下面是一个有些复杂的例子:
<@border>
<ul>
<@do_thrice>
<li><@greet person="Joe"/>
</@do_thrice>
</ul>
</@border>
输出结果:
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<ul>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
</ul>
</tr></td></table>
宏定义中的局部变量对嵌套内容是不可见的,例如:
<#macro repeat count>
<#local y = "test">
<#list 1..count as x>
${y} ${count}/${x}: <#nested>
</#list>
</#macro>
<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>
输出结果:
test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?
在宏定义中使用循环变量
用户定义指令可以有循环变量,通常用于重复嵌套内容,基本用法是:
作为nested指令的参数传递循环变量的实际值,而在调用用户定义指令时,在<@…>开始标记的参数后面指定循环变量的名字
例子:
<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count>
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
输出结果:
1. 0.5
2. 1
3. 1.5
4. 2 Last!
Ø 指定的循环变量的数目和用户定义指令开始标记指定的不同不会有问题
n 调用时少指定循环变量,则多指定的值不可见
n 调用时多指定循环变量,多余的循环变量不会被创建
(2)在模板中定义变量
在模板中定义的变量有三种类型:
plain变量:可以在模板的任何地方访问,包括使用include指令插入的模板,使用assign指令创建和替换
局部变量:在宏定义体中有效,使用local指令创建和替换
循环变量:只能存在于指令的嵌套内容,由指令(如list)自动创建;宏的参数是局部变量,而不是循环变量
局部变量隐藏(而不是覆盖)同名的plain变量;循环变量隐藏同名的局部变量和plain变量,下面是一个例子:
<#assign x = "plain">
1. ${x} <#-- we see the plain var. here -->
<@test/>
6. ${x} <#-- the value of plain var. was not changed -->
<#list ["loop"] as x>
7. ${x} <#-- now the loop var. hides the plain var. -->
<#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here -->
8. ${x} <#-- it still hides the plain var. -->
</#list>
9. ${x} <#-- the new value of plain var. -->
<#macro test>
2. ${x} <#-- we still see the plain var. here -->
<#local x = "local">
3. ${x} <#-- now the local var. hides it -->
<#list ["loop"] as x>
4. ${x} <#-- now the loop var. hides the local var. -->
</#list>
5. ${x} <#-- now we see the local var. again -->
</#macro>
输出结果:
1. plain
2. plain
3. local
4. loop
5. local
6. plain
7. loop
8. loop
9. plain2
内部循环变量隐藏同名的外部循环变量,如:
<#list ["loop 1"] as x>
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>
输出结果:
loop 1
loop 2
loop 3
loop 2
loop 1
模板中的变量会隐藏(而不是覆盖)数据模型中同名变量,如果需要访问数据模型中的同名变量,使用特殊变量global
下面的例子假设数据模型中的user的值是Big Joe:
<#assign user = "Joe Hider">
${user} <#-- prints: Joe Hider -->
${.globals.user} <#-- prints: Big Joe -->
(3)名字空间
通常情况,只使用一个名字空间,称为主名字空间
为了创建可重用的宏、变换器或其它变量的集合(通常称库),必须使用多名字空间,其目的是防止同名冲突
创建库
下面是一个创建库的例子(假设保存在lib/my_test.ftl中):
<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>
<#assign mail = "jsmith@acme.com">
使用import指令导入库到模板中,Freemarker会为导入的库创建新的名字空间,并可以通过import指令中指定的散列变量访问库中的变量:
<#import "/lib/my_test.ftl" as my>
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}
输出结果:
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
<br>Email: jsmith@acme.com</p>
jsmith@acme.com
fred@acme.com
可以看到例子中使用的两个同名变量并没有冲突,因为它们位于不同的名字空间
可以使用assign指令在导入的名字空间中创建或替代变量,下面是一个例子:
<#import "/lib/my_test.ftl" as my>
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
输出结果:
jsmith@acme.com
jsmith@other.com
数据模型中的变量任何地方都可见,也包括不同的名字空间,下面是修改的库:
<#macro copyright date>
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">
假设数据模型中的user变量的值是Fred,则下面的代码:
<#import "/lib/my_test.ftl" as my>
<@my.copyright date="1999-2002"/>
${my.mail}
输出结果:
<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
Fred@acme.com
Sruts2.X对siteMesh和FreeMarker的支持
首先struts2.X的默认模板语言是freemarker
struts2.0将SiteMesh的拦截器进行了扩展增加了对Velocity和Freemarker模板文件的支持,
org.apache.struts2.sitemesh.FreeMarkerPageFilter 应用这个 Filter拦截对freeMarker文件的请求,对服务器响应页面进行装饰,返回给客户统一的页面布局
Struts2.0将com.opensymphony.module.sitemesh.filter.PageFilter 扩展为了 org.apache.struts2.sitemesh.FreeMarkerPageFilter,以后直接使用后者就可以了
如要是使用 Velocity模板的话将web.xml文件中的 siteMesh的实现类替换为: org.apache.struts2.sitemesh.VelocityPageFilter即可.
可以在SiteMesh模板中使用struts标签,在模板中使用struts标签必须增加一个叫 ActionContextCleanUp 的过滤器,
如果在Freemarker模板文件中使用jsp标签(siteMesh标签是Jsp标签的一种)则必须在 web.xml 文件中启动JspSupportServlet,即在web.xml文件中加入如下配置片段:
<servlet>
<servlet-name>JspSupportServlet</servlet-name>
<servlet-class>org.apache.struts2.views.JspSupportServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
最后的web.xml文件中的配置如下:
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>org.apache.struts2.sitemesh.FreeMarkerPageFilter</filter-class>
</filter>
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>JspSupportServlet</servlet-name>
<servlet-class>org.apache.struts2.views.JspSupportServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
freeMarker 的模板中不能直接使用JSP标签,所以不能FreeMarker模板中使用JSP的标签<decorator:head/>等,但是FreeMarker提供了更简单的方式来输出被装饰页面的内容,
下边列出了FreeMarker提供的更简单的输出被装饰页面的方式:
${title}:输出被装饰页面的title部分
${head} :输出被装饰页面的Head部分
${body} :输出被装饰页面的body部分