`

SiteMesh工作原理

 
阅读更多

好吧!尽管这个标题有点吓人,但我并不是来摆显自己有多么的能耐,只不过是最近比较闲,而且程序员们天生爱折磨自己,所以就顺带研究了一下SiteMesh的原理。如果你是第一次听说SiteMesh,或者从未使用过它,而你又对SiteMesh感到兴趣的话,请务必先闻一闻、用一用,感受一下SiteMesh的魅力,本文并不会教你如何使用它。

总的来说,SiteMesh就是用来让你脱离<jsp:include/>标签的苦海的,它会为你自动地添加页头、脚注或者导航栏。公司里总会有人问我:你是怎么看源码的?而我总是告诉他们:如果你在高中阶段不是填鸭式学习的话,你应该会知道怎么看源码。他们总是一脸疑惑的看着我。事实上,我看源码是基于“猜想-验证”这样的步骤去做的,那么,要实现SiteMesh装饰器那样的效果,我的猜想是:

1、在装饰页面上留下类似于<dec:body/>这样的标记。

2、当jsp解释器遇到这个标记时,就把用户真正请求的页面塞进去。

然而,要做到这样的要求,要解决的问题有两个:

1、如何截取用户真正的请求页面

2、如何塞进去

幸好,这两个都不算什么大难题,我们可以使用Filter来拦截返回(Response)客户端浏览器的内容,从而实现内容的截取。第二个我们可以使用自定义JSP标签的方法实现“一遇到,则填充”这样的效果。

事实上,SiteMesh作者的想法跟我猜想的思路是一致的,SiteMesh所使用的Filter在web.xml中写得很清楚:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<web-app>
    <!-- Start of SiteMesh stuff -->
    <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>
 
    <taglib>
        <taglib-uri>sitemesh-page</taglib-uri>
        <taglib-location>/WEB-INF/sitemesh-page.tld</taglib-location>
    </taglib>
 
    <taglib>
        <taglib-uri>sitemesh-decorator</taglib-uri>
        <taglib-location>/WEB-INF/sitemesh-decorator.tld</taglib-location>
    </taglib>
</web-app>
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

没错,PageFilter就是SiteMesh用来拦截数据的类,那么再看看PageFilter类的doFilter方法:

 

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void doFilter(ServletRequest rq, ServletResponse rs, FilterChain chain)
            throws IOException, ServletException {
 
   if (rq.getAttribute(FILTER_APPLIED) != null) {
            // ensure that filter is only applied once per request
      chain.doFilter(rq, rs);
   }
   else {
      HttpServletRequest request = (HttpServletRequest) rq;
      HttpServletResponse response = (HttpServletResponse) rs;
 
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
 
      // force creation of the session now because Tomcat 4 had problems with
      // creating sessions after the response had been committed
      if (Container.get() == Container.TOMCAT) {
         request.getSession(true);
      }
 
      // parse data into Page object (or continue as normal if Page not parseable)
      Page page = parsePage(request, response, chain);
 
      if (page != null) {
         page.setRequest(request);
 
         Decorator decorator = factory.getDecoratorMapper().getDecorator(request, page);
         if (decorator != null && decorator.getPage() != null) {
            applyDecorator(page, decorator, request, response);
            page = null;
            return;
         }
 
         // if we got here, an exception occured or the decorator was null,
         // what we don't want is an exception printed to the user, so
         // we write the original page
         writeOriginal(response, page);
         page = null;
      }
   }
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

老实说,虽然代码注释很烂,但是基本的逻辑都体现在了doFilter方法里了,如果看代码不能让你拨开云雾的话,我还在网上扒了一幅图片:

 

clip_image001

 

当用户请求home.jsp,并且服务器处理完毕正准备返回数据之时,它被SiteMesh Filter拦截了下来,并且把数据包装成一个Page对象,具体是Page page = parsePage(request, response, chain)的调用,然后,它会去查询decorators.xml文件,看看该页面是否需要装饰[if (decorator != null && decorator.getPage() != null)]?是,则应用装饰器[applyDecorator(page, decorator, request, response)],否则,就发送原来的没经过装饰的页面[writeOriginal(response, page);]。

 

然而,我们到底如何做才能把返回内容剥离出来呢?答案就是使用自定义的响应结果包装器,其实就是一个继承了HttpServletResponseWrapper类的java类,如下:

 

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
26
27
28
29
30
31
32
33
34
35
36
37
package servlet.util;
import java.io.CharArrayWriter;
/**
* 自定义一个响应结果包装器,将在这里提供一个基于内存的输出器来存储所有
* 返回给客户端的原始HTML代码。
* @author lee
*/
public class MyResponseWrapper extends HttpServletResponseWrapper {
   private PrintWriter cacheWriter;
   private CharArrayWriter bufferWriter;//用于保存截获的jsp内容
  
  
   public MyResponseWrapper(HttpServletResponse response) {
      super(response);
      bufferWriter = new CharArrayWriter();
      // 这个是包装PrintWriter的,让所有结果通过这个PrintWriter写入到bufferedWriter中
      cacheWriter = new PrintWriter(bufferWriter);
   }
 
   /**
    *当一个继承了HttpServletResponseWrapper的包装器复写了getWriter()方法时
    *tomcat会把响应的内容塞入自定义的PrintWriter(cacheWriter)中
    */
   @Override
   public PrintWriter getWriter() throws IOException {
      return cacheWriter;
   }
  
   /**
    * 获取原始的HTML页面内容。
    * @return
    */
   public String getResult(){
      return bufferWriter.toString();
   }
 
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

一个很简单的类,然后,只需要在doFilter方法中如下调用,就可以截取jsp的页面内容了:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void doFilter(ServletRequest servletrequest,
   ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException {
     
   // 使用我们自定义的响应包装器来包装原始的ServletResponse
   MyResponseWrapper wrapper = new MyResponseWrapper((HttpServletResponse) servletresponse);
     
   // 这句话非常重要,注意看到第二个参数是我们的包装器而不是原始的servletresponse
   //这样容器才会把响应内容写入自定义的包装器中
   filterchain.doFilter(servletrequest, wrapper);
     
   // 截获的结果并进行处理
   String result = wrapper.getResult();
   ......
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

而SiteMesh就是这样做的,只不过复杂点罢了,你可以去看看PageFilter类的parsePage方法。

好了,现在我们可以截取jsp页面了,剩下的就是考虑如何把这些内容塞进我们的标签<dec:body/>中,而这就属于自定义JSP标签的范畴了,网上一搜一大把,总的来说就是在web.xml中指定标签库,又在标签库中指定处理类:

web.xml

1
2
3
4
5
6
7
8
9
<web-app>
......
   <!-- 自定义JSP标签  -->
   <taglib>
      <taglib-uri>http://customtag.com</taglib-uri>
      <taglib-location>/mytld/custom.tld</taglib-location>
   </taglib>
......
</web-app>
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

然后,你需要在项目的根目录下建立好mytld文件夹,在里面建立好custom.tld文件,使用任何一种编辑器打开并敲入下面的代码:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
   <tlibversion>1.0</tlibversion>
   <jspversion>1.1</jspversion>
   <shortname>Custom Tags</shortname>
   <uri>http://customtag.com</uri>
  
   <tag>
      <name>body</name>
      <tagclass>custom.tag.BodyTag</tagclass>
      <bodycontent>JSP</bodycontent>
   </tag>
</taglib>
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

在custom.tld中我们指定了遇到<dec:body/>标签就交给BodyTag处理,而BodyTag事实上就是一个继承了TagSupport的java类,并且,你需要重写doStartTag方法和doEndTag方法:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package custom.tag;
import javax.servlet.jsp.tagext.TagSupport;
public class BodyTag extends TagSupport{
   public int doStartTag(){
      try{
         String reqPage = (String) pageContext.getAttribute("reqPage", pageContext.REQUEST_SCOPE);
         if(reqPage == null){
            pageContext.getOut().print("标签开始了<font color=\"red\">Hello</font>");
         }else{
            pageContext.getOut().print(reqPage);
         }
      }catch (Exception e) {
         e.printStackTrace();
      }
      return EVAL_BODY_INCLUDE;
   }
  
   public int doEndTag(){
      return EVAL_PAGE;
   }
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

嗯,到底把截获的jsp页面放到哪里,才能让BodyTag类取到并使用呢?我把它放到了request对象当中:

image

 

下面看看自定义的MyFilter类的doFilter方法:

 

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
26
27
28
29
30
31
32
33
34
package servlet.demo;
import java.io.IOException;
public class MyFilter implements Filter {
   private ServletContext servletContext = null;
  
   public void destroy() {
 
   }
 
   public void doFilter(ServletRequest servletrequest,
         ServletResponse servletresponse, FilterChain filterchain)
         throws IOException, ServletException {
     
      // 使用我们自定义的响应包装器来包装原始的ServletResponse
      MyResponseWrapper wrapper = new MyResponseWrapper((HttpServletResponse) servletresponse);
     
      // 这句话非常重要,注意看到第二个参数是我们的包装器而不是原始的response
      //这样容器才会把响应内容写入自定义的包装器中
      filterchain.doFilter(servletrequest, wrapper);
     
      // 处理截获的结果并进行处理
      String result = wrapper.getResult();
      System.out.println(result);
      //把jsp页面放到request中
      servletrequest.setAttribute("reqPage", result);
      //把smart.jsp包含进请求中,这一步会触发jsp解释器去解释smart.jsp页面
      //当遇上<dec:body/>时,塞入result
      servletrequest.getRequestDispatcher("decorators/smart.jsp").include(servletrequest, servletresponse);
   }
 
   public void init(FilterConfig filterconfig) throws ServletException {
      servletContext = filterconfig.getServletContext();
   }
}
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

这里的servletrequest.getRequestDispatcher("decorators/smart.jsp").include(servletrequest,servletresponse)很重要,因为程序执行到这里会触发jsp解释器去解释jsp页面,而smart.jsp就是我们的装饰页面:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ taglib uri="http://customtag.com" prefix="dec"%>
<html>
<head>
   <title>欢迎访问 </title>
</head>
<body>
   <div style="margin-bottom:10px;padding:6px;border:1px solid gray;">我这里是头部,页面装饰器头部定义</div>
  
   <div style="margin-bottom:10px;padding:6px;border:1px solid gray;">
      功能菜单:<a href="#"/>用户管理</a>
   </div>
  
   <!-- 这里是操作信息提示区域 -->
   <div></div>
  
   <!-- 这里是功能的内容区域 -->
   <div>
      <dec:body />
   </div>
  
   <div style="margin-top:10px;padding:6px;border:1px solid gray;">我这里是尾部,页面装饰器尾部定义</div>
</body>
</html>
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

当jsp解释器解释smart.jsp的过程中遇到了<dec:body/>,就会跑到BodyTag中执行标签解释工作,此时,我们就可以把早已准备好的用户真正请求的页面内容塞进去:

 

1
2
3
4
//从request中取出数据
String reqPage = (String) pageContext.getAttribute("reqPage", pageContext.REQUEST_SCOPE);
//填充
pageContext.getOut().print(reqPage);
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

SiteMesh正是使用这种方式实现自动装饰功能,这里面并没有什么高深的技术(可能是我还没有发现)。

 

现在,写一个需要应用装饰的页面index.jsp:

 

1
2
<!-- index.jsp -->
<h1>Hello World!!</h1>
<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

在index.jsp中你甚至不用写<body/>标签

 

最终结果:

image

分享到:
评论

相关推荐

    sitemesh教程-页面装饰技术原理及应用

    Sitemesh是一种页面装饰技术:它通过过滤器(filter)来拦截页面访问,据被访问页面的URL找到合适的装饰模板等等,感兴趣的朋友可以了解下哦

    Struts2的工作原理和流程

    2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin) 3 接着FilterDispatcher被调用,...

    Struts2执行原理

    首先客户端发送HttServletErquest的请求,这个请求经过一系列的过滤器链(Filter),这里是有顺序的,首先经过ActionContext CleanUp,然后再经过其他过滤器(Othter Filters、SiteMesh等),最后再到...

    基于jbpm与activiti的工作流平台技术架构介绍

    14.Other: Log4j,Velocity,Sitemesh,Jfreechart,Jforum,Solr 15.Maven 2 3.BPMX3的组件构建方式 BPMX3同时也是基于组件构构建,整个系统的构建如下所示: 【图三】BPMX3组件构建 系统提供在线流程设计器,...

    SSH的jar包.rar

    下面粗略的分析下FilterDispatcher工作流程和原理:FilterDispatcher进行初始化并启用核心doFilter。 Hibernate 的原理 1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件 2.由hibernate....

    struts2开发文档

    struts2的所有知识点 流程 原理 一个请求在Struts2框架中的处理大概分为以下几个步骤: 1 客户端初始化一个指向Servlet容器(例如Tomcat)的请求; 2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个...

    struts2流程与流程图

     请求被提交到一系列(主要是3层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、 FilterDispatcher)。注意:这里是有顺序的,先ActionContext CleanUp,再其他过滤器(Othter ...

    struts2讲义_吴峻申

    1.2.8 SiteMesh页面布局框架简介 17 1.3 我们为什么要用Struts2 17 1.4 Web项目中使用Struts2初探 20 第2章 Web基础技术简介 31 2.1 B/S和C/S系统区别 31 2.2 JSP和Servlet介绍 32 2.3 XML知识介绍 34 第3章 Struts2...

    Struts2 in action中文版

    1.3.3 Struts 2的工作原理 12 1.4 小结 14 第2章 初识Struts 2 16 2.1 声明性架构 16 2.1.1 两种配置 16 2.1.2 声明架构的两种方式 17 2.1.3 智能默认值 20 2.2 简单的HelloWorld示例 20 2.2.1 部署示例应用程序 20...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    5.5.3 RMI的基本原理 220 5.6 同时作为客户端和服务器的 RMI程序 222 5.6.1 开发客户端程序 222 5.6.2 开发服务器端程序 223 5.7 本章小结 225 第6章 利用JMS实现企业消息处理 226 6.1 面向消息的架构和JMS概述 227 ...

Global site tag (gtag.js) - Google Analytics