使用 SiteMesh3 完善页面布局

在网页开发中,大部分网页都具有相同的页头,页尾,菜单等模块。 一般情况下我们会将这些共用的代码单独抽取成一个页面,然后进行包含。 虽然这样能够达到代码复用的效果,但是如果引入的页面过多,一来会带来修改不变的效果,二来依然会形成多个页面使用相同的代码 (页面包含代码),此时我们可以使用 SiteMesh3 来妥善解决这个问题

最初页面形式

最初页面形式

使用 JSP 进行页面包含

页面包含

进行页面包含后,能达到一些共用页面的代码复用,但是如果页面布局复杂的话,会存在大量的页面包含代码,依然给我们带来了不便

SiteMesh3 方式

SiteMesh3 使用演示

使用 SiteMesh3 时需要先定义一个装饰器,在这个装饰器中我们可以定义页面的布局,然后配置动态内容输出位置即可
示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE>
<html lang="zh-cmn-Hans">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

<!--<sitemesh:write property="title"/> 会输出原始页面的 title 标签里面的内容 -->
<title><sitemesh:write property="title"/></title>

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="renderer" content="webkit"/>
<link rel="stylesheet" type="text/css" href="/static/css/web.css"/>

<!--<sitemesh:write property="head"/> 会输出原始页面 head 标签里面的内容 (不包括 title 标签)-->
<sitemesh:write property="head"/>
</head>
<body>
<header>header</header>

<!--<sitemesh:write property="body"/> 会输出原始页面 body 标签里面的内容 -->
<sitemesh:write property="body"/>

<footer>footer</footer>
<script type="text/javascript" src="/static/js/web.js"></script>
</body>
</html>

假如此时存在一个这样的原始页面

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE>
<html>
<head>
<title>title</title>
<meta name="keywords" content="SiteMesh3">
</head>
<body>
hello,world
</body>
</html>

经过 SiteMesh3 装饰后的页面如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE>
<html lang="zh-cmn-Hans">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

<title>title</title>

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="renderer" content="webkit"/>
<link rel="stylesheet" type="text/css" href="/static/css/web.css"/>

<meta name="keywords" content="SiteMesh3">

</head>
<body>

<header>header</header>
hello,world
<footer>footer</footer>
<script type="text/javascript" src="/static/js/web.js"></script>
</body>
</html>

可以发现使用 SiteMesh3 进行装饰能够让我们更加专注于一些与页面独有的代码逻辑,能避免相同的代码在多个页面重复出现

SiteMesh3 使用说明

通过上面的一个小例子,我们可以发现使用 SiteMesh3 需要一个装饰器页面。 由此可以牵扯出另外几个问题,对哪些页面进行装饰? 使用哪个装饰页面装饰?通过以下配置可以完成这些疑问
首先我们需要引入 SiteMesh3 相关的 jar 包

1
2
3
4
5
<dependency>
<groupId>org.sitemesh</groupId>
<artifactId>sitemesh</artifactId>
<version>3.0.1</version>
</dependency>

其次,SiteMesh3 会对一些页面进行装饰,所以我们需要添加一个过滤器来进行页面过滤

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>org.sitemesh.config.ConfigurableSiteMeshFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<!-- 如果项目中的页面地址以 .do 或者 .action 结尾可以使用 *.do 或 *.action-->
<url-pattern>/*</url-pattern>
</filter-mapping>

同时我们还需要配置装饰器,要装饰的页面,不需要装饰的页面等信息
WEB-INF 目录下创建一个 sitemesh3.xml 文件,请确保路径正确,SiteMesh3 会根据 /WEB-INF/sitemesh3.xml 加载文件
文件内容如下

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<sitemesh>
<!-- path 是指要进行装饰的页面 如 /index.jsp 对应 http://localhost:8080/index.jsp -->
<!-- decorator 是指装饰器页面 /decorators/default.jsp 对应位置为 src/main/webapp/decorators/default.jsp-->
<!-- sitemesh3 会在 index.jsp 页面返回时提取 title head body 中的内容, 然后在 default.jsp 根据输出标签进行对应输出 -->
<mapping path="/index.jsp" decorator="/decorators/default.jsp"/>
</sitemesh>

此时我们可以将上面演示中的文件内容建立 index.jspdefault.jsp 页面,然后进行测试访问,会发现能达到演示中的效果

SiteMesh3 高级配置

通过上面的简单配置我们可以实现一个最基本的页面装饰,与此同时 SiteMesh3 还支持一些更为高级的配置

  • 默认装饰器

    1
    2
    3
    <!-- 如果不填写 path 路径, 则 SiteMesh3 会在找不到匹配的装饰器时, 使用这个装饰器进行装饰 -->
    <!-- 推荐在该页面中定义一些全网站共用的代码, 如: 网站统计相关脚本, 浏览器渲染方式等 meta 标签 -->
    <mapping decorator="/decorators/default.jsp"/>
  • 多个装饰器

    1
    2
    3
    4
    5
    6
    <mapping>
    <path>/multi.jsp</path>
    <decorator>/decorators/multi_1.jsp</decorator>
    <decorator>/decorators/multi_2.jsp</decorator>
    <decorator>/decorators/multi_3.jsp</decorator>
    </mapping>

装饰器会按照配置的先后顺序进行装饰
假如
multi.jsp 中 body 内容为 0,multi_1.jsp 中内容为 1 同时在下面输出原始页面中的 body 内容

1
2
3
4
<body>
<h3>1</h3>
<sitemesh:write property="body"/>
</body>

multi_2.jsp 中内容为 2 同时在下面输出原始页面中的 body 内容

最终返回的页面内容为
2
1
0

  • 不需要装饰

    1
    2
    3
    <!-- SiteMesh3 会先判断是否不需要装饰, 然后再判断是否存在匹配的装饰器 -->
    <!-- /exclude/* 会对所有以 /exclude 开头的 url 都不会进行装饰 包括 /exclude/index.jsp,/exclude/item/index.jsp -->
    <mapping path="/exclude/*" exclue="true"/>
  • MIME 类型

    1
    2
    3
    4
    5
    6
    7
    <!-- 
    默认情况下, SiteMesh3 只对响应头 Content-Type 中包含 text/html 的页面进行装配
    如果会需要装配其他格式的页面, 添加多个 mime-type 标签即可
    -->
    <mime-type>text/html</mime-type>
    <mime-type>application/vnd.wap.xhtml+xml</mime-type>
    <mime-type>application/xhtml+xml</mime-type>
  • 自定义输出标签

    1
    2
    3
    4
    5
    6
    7
    <!--
    输出标签格式 <sitemesh:write property="body"/>
    SiteMesh3 默认支持 title, head, body 三个标签, 如果需要支持其他标签, 可以通过实现 org.sitemesh.content.tagrules.TagRuleBundle 接口中的 install 方法来完成对标签的拓展
    -->
    <content-processor>
    <tag-rule-bundle class="com.github.ghthou.learning.sitemesh3.ExpandTagRuleBundle"/>
    </content-processor>

ExpandTagRuleBundle 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExpandTagRuleBundle implements TagRuleBundle {

@Override
public void install(State defaultState, ContentProperty contentProperty, SiteMeshContext siteMeshContext) {
defaultState.addRule("header", new ExportTagToContentRule(siteMeshContext, contentProperty.getChild("header"), false));
defaultState.addRule("menu", new ExportTagToContentRule(siteMeshContext, contentProperty.getChild("menu"), false));
defaultState.addRule("footer", new ExportTagToContentRule(siteMeshContext, contentProperty.getChild("footer"), false));
}

@Override
public void cleanUp(State defaultState, ContentProperty contentProperty, SiteMeshContext siteMeshContext) {

}
}

此时可以使用 <sitemesh:write property="header"/> 标签在装饰器中输出原始页面的 header 标签值

  • 其他说明

    • title, head, body 标签的实现请参考 org.sitemesh.content.tagrules.html.CoreHtmlTagRuleBundle
    • title, head, body 等自定义标签只会提取原始页面中的第一个找到的标签
    • 默认配置文件路径为 /WEB-INF/sitemesh3.xml ,如果需要自定义配置路径,请在配置 filter 时配置 configFile 属性,如将 sitemesh3.xml 配置文件放在 resources 文件夹中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <filter>
      <filter-name>sitemesh</filter-name>
      <filter-class>org.sitemesh.config.ConfigurableSiteMeshFilter</filter-class>
      <!--设置配置文件路径 默认为 /WEB-INF/sitemesh3.xml -->
      <init-param>
      <param-name>configFile</param-name>
      <param-value>/WEB-INF/classes/sitemesh3.xml</param-value>
      </init-param>
      </filter>
    • 如果装饰器是 html 文件会存在中文乱码的问题,在 SpringMVC 中可在 web.xml 文件配置如下过滤器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <filter>
      <filter-name>CharacterEncodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
      </init-param>
      <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
      </init-param>
      </filter>
      <filter-mapping>
      <filter-name>CharacterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
      </filter-mapping>
    • 在 SpringMVC 中因为 SiteMesh3 需要直接访问到装饰页面,所以需要增加如下标签

      1
      2
      3
      <mvc:default-servlet-handler/>
      <!-- 或者配置 url 与文件夹映射关系 -->
      <mvc:resources mapping="/decorators/**" location="/decorators/"/>
    • 如果装饰器为 JSP 页面,则可以使用 EL 表达式获取原始页面中的属性
      假如在 SpringMVC 中设置如下属性

      1
      2
      3
      4
      5
      @RequestMapping(value = "/index")
      public String index(Model model) {
      model.addAttribute("date", new Date().toLocaleString());
      return "index";
      }

      则可以在方法返回页面 /index.jsp 和该 url 的装饰页面 /decorators/default.jsp 中使用 EL 表达式获取 date 属性

参考资料

SiteMesh3 官网文档
SiteMesh3 GitHub

源码

GitHub