引入:

最近在review别的团队的代码,看到他们团队使用了sitemesh,以前对这个不是很了解,刚好借助他们源代码,这里简单研究了下:


简单介绍:

其实sitemesh使用了页面装饰技术,它主要就是用一个或者多个装饰器应用于某些给定页面,最终产生被装饰器装饰后的页面。

具体过程是:

(1)sitemesh通过Filter来拦截页面访问 (可以猜想:肯定会有个url-pattern,表明此url被适用于某装饰器,等会具体讲 )

(2)一旦拦截到,则根据被访问的url来找到合适的装饰模板(可以猜想:肯定有一个模板定义文件,在其中定义了模板和模板使用的url之间的映射关系)

(3)提取被访问的页面的内容,放到装饰模板中给定的位置(这几乎是废话,因为这是组合页面技术,肯定输入有2个,一个是模板,一个是被访问页面,模板肯定会给出一些可配置的点,然后用placeholder表示,然后这些placeholder中放入的内容肯定由真实页面提供,放真实页面的内容肯定有个标记,这个标记符合模板上的标记从而不会找错,等会可以验证)

(4)装饰后的页面发送给客户端。


以上是我对于这个框架的开始的猜想,接下来就是去验证这些猜想。


实践:

(1)首先,如何做到sitemesh通过filter来拦截页面访问呢?根据常识,一般web应用过滤器都是定义在web.xml中的,所以我们去找:


<!-- sitemesh config -->
    <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>

从这里可以很清楚的看到,我们配置了 sitemesh过滤器,然后要对所有的页面都适用这个过滤器,当然了,要过滤器生效,还必须有相应的jar包,并且放在WEB-INF/lib下,这个是常识,因为他们项目中使用了maven,所以我们肯定会在pom.xml中找到对应的depenency:

<!-- sitemesh -->
        <dependency>
            <groupId>opensymphony</groupId>
            <artifactId>sitemesh</artifactId>
            <version>2.4.2</version>
        </dependency>


(2)现在我们来看第二步,肯定有个模板定义文件,定义了模板文件和其适用的文件之间的关系。这里就有2个概念了,一个是一般文件(可以说比较素的文件), 一种是模板文件(它定义了比较花哨的结构和外观),素文件我们不讲了,就是一般页面(比如jsp),然后模板文件,肯定是带有placeholder的页面文件,所以我们找到了这些文件:

Sitemesh  初探_web

显然,在他们的项目中,这些模板文件是放在WEB-INF/decorators中的,并且从这里可以看出他们定义了2个模板文件,一个叫blog_main.jsp,另一个叫main.jsp,然后他们的一般文件都是放在WEB-INF/jsp中的来加强安全性。我们随便打开一个模板文件,比如blog_main.jsp:

<!DOCTYPE HTML>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator"%>
<%@ page language="java" contentType="text/html; charset=utf-8"  pageEncoding="utf-8"%>
<html lang="en">
    <head>
                                                                                                                                                                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                                                                                                                                     
        <link rel="icon" href="${contextPath}/p_w_picpaths/favicon.ico" type="p_w_picpath/x-icon" />
        <link rel="shortcut icon" href="${contextPath}/p_w_picpaths/favicon.ico" type="p_w_picpath/x-icon" />
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="${cssRootPath}/common.css">
        <jsp:include page="${jspRootPath}/includes/global/incScript.jsp"/>
        <!-- 从被装饰页面获取title标签内容,并设置默认值-->
        <title><decorator:title default="Blog - Shokay"/></title>
        <!-- 从被装饰页面获取head标签内容 -->
        <decorator:head/>
    </head>
    <body>
        <jsp:include page="${jspRootPath}/includes/common/incPageTopHideBar.jsp" />
        <jsp:include page="${jspRootPath}/includes/common/incPageTopBar_blog.jsp" />
        <div class="main-body">
             <decorator:body />
        </div>
                                                                                                                                                                                                                                                                                                                                                                                     
    </body>
                                                                                                                                                                                                                                                                                                                                                                                 
    <jsp:include page="${jspRootPath}/includes/common/incPageFooterBlog.jsp" />
</html>


果然和我们猜想一样,这个页面是一个有占位符的文件,所以是模板文件,并且模板文件用的标签前缀都是 <decorator>,所以这个模板文件中有3个可插入内容的点,一个是<decorator:title/>,一个是<decorator:head/>,一个是<decorator:body/>, 并且查询相关文章可以知道,整合素文件的过程是从素文件中分别提取出<title>,<head><body> 元素并且插入到相应的位置,如果没有相应的内容则使用后面的default默认值。当然了,<decorator>标记还有其他的内容,我们这里不一一阐述。


当然了,国际化惯例,为了使用这个标记,在页首必须声明标记前缀。从这里可以看出来,这个前缀来自http://www.opensymphony.com/sitemesh/decorator这个标记库


然后我们去探讨模板和适用页面之间的映射关系了,常识告诉我们肯定是在xml文件中配置,我们很快找到了这个映射文件,WEB-INF/decorators.xml


<?xml version="1.0" encoding="ISO-8859-1"?>
<decorators defaultdir="/WEB-INF/decorators">
    <!-- Any urls that are excluded will never be decorated by Sitemesh -->
                                                                                                                                                                                                                                            
    <excludes>
        <pattern>/changepassword*</pattern>
        <pattern>/*Ajax</pattern>
        <pattern>/*ajax</pattern>
        <pattern>/p_w_picpath</pattern>
        <pattern>/checkout/*</pattern>
        <pattern>/admin/search/*</pattern>
        <pattern>/index</pattern>
    </excludes>
                                                                                                                                                                                                                                            
    <!-- blog -->
    <decorator name="blog" page="blog_main.jsp">
        <pattern>/blog/*</pattern>
    </decorator>
    <!-- main -->
    <decorator name="main" page="main.jsp">
        <pattern>/*</pattern>
    </decorator>
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
    <decorator name="none">
        <pattern>/index.jsp</pattern>
    </decorator>
</decorators>


果然和设想一样,我们在WEB-INF/decorators中定义的2个模板文件在这里都有定义,并且每个模板文件(对应某个装饰器)都有给定的<pattern>,表明此装饰器使用的URL集合,我们找到了刚才的blog_main.jsp这个装饰页面使用的集合是/blog/ 开头的任意url。

Sitemesh  初探_sitemesh_02

当然了, 这里还可以使用<exclude>来控制那些页面不受到装饰模板的控制。


也许你会问,我们这个定义文件是否一定要叫WEB-INF/decorators.xml呢?其实不一定的,默认是在WEB-INF下的decorators.xml,(约定优于配置) ,这个约定在sitemesh.jar中。如果你要显式定制它的位置的话,那么它在sitemesh.xml中配置了位置:

<sitemesh>
    <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
    <excludes file="${decorators-file}"/>
    <page-parsers>
        <parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
    </page-parsers>
    <decorator-mappers>
...
</sitemesh>

从上可以看出,用于装饰模板的文件是定义在/WEB-INF/decorators.xml中。