好吧!尽管这个标题有点吓人,但我并不是来摆显自己有多么的能耐,只不过是最近比较闲,而且程序员们天生爱折磨自己,所以就顺带研究了一下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 >
|
没错,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 ;
}
}
} |
老实说,虽然代码注释很烂,但是基本的逻辑都体现在了doFilter方法里了,如果看代码不能让你拨开云雾的话,我还在网上扒了一幅图片:
当用户请求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();
}
} |
一个很简单的类,然后,只需要在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();
......
} |
而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-location >/mytld/custom.tld</ taglib-location >
</ taglib >
...... </ web-app >
|
然后,你需要在项目的根目录下建立好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 >
< tag >
< name >body</ name >
< tagclass >custom.tag.BodyTag</ tagclass >
< bodycontent >JSP</ bodycontent >
</ tag >
</ taglib >
|
在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;
}
} |
嗯,到底把截获的jsp页面放到哪里,才能让BodyTag类取到并使用呢?我把它放到了request对象当中:
下面看看自定义的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();
}
} |
这里的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 >
|
当jsp解释器解释smart.jsp的过程中遇到了<dec:body/>,就会跑到BodyTag中执行标签解释工作,此时,我们就可以把早已准备好的用户真正请求的页面内容塞进去:
1
2
3
4
|
//从request中取出数据 String reqPage = (String) pageContext.getAttribute( "reqPage" , pageContext.REQUEST_SCOPE);
//填充 pageContext.getOut().print(reqPage); |
SiteMesh正是使用这种方式实现自动装饰功能,这里面并没有什么高深的技术(可能是我还没有发现)。
现在,写一个需要应用装饰的页面index.jsp:
1
2
|
<!-- index.jsp --> < h1 >Hello World!!</ h1 >
|
在index.jsp中你甚至不用写<body/>标签
最终结果:
相关推荐
Sitemesh是一种页面装饰技术:它通过过滤器(filter)来拦截页面访问,据被访问页面的URL找到合适的装饰模板等等,感兴趣的朋友可以了解下哦
2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin) 3 接着FilterDispatcher被调用,...
首先客户端发送HttServletErquest的请求,这个请求经过一系列的过滤器链(Filter),这里是有顺序的,首先经过ActionContext CleanUp,然后再经过其他过滤器(Othter Filters、SiteMesh等),最后再到...
14.Other: Log4j,Velocity,Sitemesh,Jfreechart,Jforum,Solr 15.Maven 2 3.BPMX3的组件构建方式 BPMX3同时也是基于组件构构建,整个系统的构建如下所示: 【图三】BPMX3组件构建 系统提供在线流程设计器,...
下面粗略的分析下FilterDispatcher工作流程和原理:FilterDispatcher进行初始化并启用核心doFilter。 Hibernate 的原理 1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件 2.由hibernate....
struts2的所有知识点 流程 原理 一个请求在Struts2框架中的处理大概分为以下几个步骤: 1 客户端初始化一个指向Servlet容器(例如Tomcat)的请求; 2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个...
请求被提交到一系列(主要是3层)的过滤器(Filter),如(ActionContextCleanUp、其他过滤器(SiteMesh等)、 FilterDispatcher)。注意:这里是有顺序的,先ActionContext CleanUp,再其他过滤器(Othter ...
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...
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...
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 ...