SiteMesh工作原理

2024-08-29 04:48
文章标签 工作 原理 sitemesh

本文主要是介绍SiteMesh工作原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

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

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

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

2、如何塞进去

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

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

 

<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方法:

 

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 requestchain.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 committedif (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 pagewriteOriginal(response, page);page = null;}}
}

 

老实说,虽然代码注释很烂,但是基本的逻辑都体现在了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类,如下:

 

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)中*/@Overridepublic PrintWriter getWriter() throws IOException {return cacheWriter;}/*** 获取原始的HTML页面内容。* @return*/public String getResult(){return bufferWriter.toString();}}

 

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

 

public void doFilter(ServletRequest servletrequest,ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException {// 使用我们自定义的响应包装器来包装原始的ServletResponseMyResponseWrapper 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

<web-app>
......<!-- 自定义JSP标签  --><taglib><taglib-uri>http://customtag.com</taglib-uri><taglib-location>/mytld/custom.tld</taglib-location></taglib>
......
</web-app>

 

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

 

<?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>

 

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

 

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对象当中:

image

 

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

 

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 {// 使用我们自定义的响应包装器来包装原始的ServletResponseMyResponseWrapper 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/>时,塞入resultservletrequest.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就是我们的装饰页面:

 

<%@ 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中执行标签解释工作,此时,我们就可以把早已准备好的用户真正请求的页面内容塞进去:

 

//从request中取出数据
String reqPage = (String) pageContext.getAttribute("reqPage", pageContext.REQUEST_SCOPE);
//填充
pageContext.getOut().print(reqPage);

 

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

 

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

 

<!-- index.jsp -->
<h1>Hello World!!</h1>

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

 

最终结果:

image

这篇关于SiteMesh工作原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1116884

相关文章

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

LiteFlow轻量级工作流引擎使用示例详解

《LiteFlow轻量级工作流引擎使用示例详解》:本文主要介绍LiteFlow是一个灵活、简洁且轻量的工作流引擎,适合用于中小型项目和微服务架构中的流程编排,本文给大家介绍LiteFlow轻量级工... 目录1. LiteFlow 主要特点2. 工作流定义方式3. LiteFlow 流程示例4. LiteF

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

详解如何使用Python构建从数据到文档的自动化工作流

《详解如何使用Python构建从数据到文档的自动化工作流》这篇文章将通过真实工作场景拆解,为大家展示如何用Python构建自动化工作流,让工具代替人力完成这些数字苦力活,感兴趣的小伙伴可以跟随小编一起... 目录一、Excel处理:从数据搬运工到智能分析师二、PDF处理:文档工厂的智能生产线三、邮件自动化:

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

基于Python开发一个有趣的工作时长计算器

《基于Python开发一个有趣的工作时长计算器》随着远程办公和弹性工作制的兴起,个人及团队对于工作时长的准确统计需求日益增长,本文将使用Python和PyQt5打造一个工作时长计算器,感兴趣的小伙伴可... 目录概述功能介绍界面展示php软件使用步骤说明代码详解1.窗口初始化与布局2.工作时长计算核心逻辑3

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事