深入讲解JSP 2.0下的动态内容缓存技术
来源: 山里娃信息网
 新闻题要: 内容缓存是Web应用中最普通的优化技术之一,例如,可以使用一个自定义地JSP标签——我们将之命名为<jc:cache>——由<jc:cache>和</jc:cache>将每一个需要被缓存的页面片段封装起来。任何自定义标签可以控制它所包含部分 (也即预先封装的页面片段)在何时执行,并且动态输出结果可以被捕获。<jc:cache>标签使得JSP容器(例如Tomcat)只生成内容一次,作为应用程序范围内的JSP变量,来存储每一个缓存片段。每次JSP页面被执行时,自定义标签将缓存页面片段载入而无需再次执行JSP代码来生成输出结果。作为Jakarta工程的一个部分,标签库的开发使用了这项技术。当被缓存内容无需被每一个用户或者请求所定制的时候,它工作的十分良好。

内容缓存是Web应用中最普通的优化技术之一,例如,可以使用一个自定义地JSP标签——我们将之命名为<jc:cache>——由<jc:cache>和</jc:cache>将每一个需要被缓存的页面片段封装起来。任何自定义标签可以控制它所包含部分 (也即预先封装的页面片段)在何时执行,并且动态输出结果可以被捕获。<jc:cache>标签使得JSP容器(例如Tomcat)只生成内容一次,作为应用程序范围内的JSP变量,来存储每一个缓存片段。每次JSP页面被执行时,自定义标签将缓存页面片段载入而无需再次执行JSP代码来生成输出结果。作为Jakarta工程的一个部分,标签库的开发使用了这项技术。当被缓存内容无需被每一个用户或者请求所定制的时候,它工作的十分良好。

这篇文章对上面描述的技术做了改进,通过使用JSP 2.0表达式语言(EL),允许JSP页面为每一个请求和用户定制缓存内容。缓存页面片段可以包含未被JSP容器赋值的JSP表达式,在每一次页面被执行时,由自定义标签来确定这些表达式的值。因此,动态内容的建立被最优化,但是缓存片段可以含有部分由每一个请求使用本机JSP表达式语言产生的内容。通过 JSP 2.0 EL API的帮助,Java开发者可以用表达式语言来使之成为可能。

内容缓存VS数据缓存

内容缓存不是唯一的选择。例如, 从数据库中提取的数据同样可以被缓存。事实上,由于存储的信息中不包含HTML markup,以及要求较少的内存,数据缓存可能更加高效率。然而在很多情况下,内存缓存更容易实现。假设在某个案例总,一个应用由大量事务对象,占用重要的CPU资源,产生复杂的数据,并且用JSP页面来呈现这些数据。工作一切良好,直到某天突然地服务器的负载增加,需要一个紧急解决方案。这时在事务对象和呈现表达层之间建立一个缓存层,时一个非常不错和有效的方案。但是必须非常快速和流畅地修改缓存动态内容的JSP页面。相对于简单的JSP页面编辑,应用程序的业务逻辑变化通常要求更多的工作量和测试;另外,如果一个页面从多个复合源聚合信息时,Web层仅有少量的改变。问题在于,当缓存信息变得失去时效时,缓存空间需要被释放,而事务对象应该知道何时发生这种情况。然而,选择实现内容缓存还是数据缓存,或者其他的优化技术,有很多不得不考虑的因素,有时是所开发的程序所特殊要求的。    数据缓存和内容缓存没有必要互相排斥,它们可以一起使用。例如,在数据库驱动的应用中;从数据库中提取出来的数据,和呈现该数据的HTML分别被缓存起来。这与使用JSP实时生成的模板有些相似。这篇文章中讨论的基于EL API技术说明如何使用JSP EL来将数据载入到呈现模板中。

使用JSP变量缓存动态内容

每当实现一个缓存机制是,都需要一个存储缓存对象的方法,在这篇文章中涉及的是 String类型的对象。 一种选择是使用一个对象——缓存框架结构,或者使用Java maps来实现自定义的缓存方案。JSP已经拥有了称为“scoped attributes”或“JSP variables”来提供ID——object映射,这正是缓存机制所需要的。对于使用page或者request scope,这是没有意义的,而在应用范围内,这是一个很好的存储缓存内容的位置, 因为它被所有的用户和页面共享。当每一个用户需要单独缓存时,Session scope也可以被使用,但这不是很有效率。JSTL标签库可以被是与那个来缓存内容,通过使用JSP变量正如下例所示:

<CCID_NOBR>

<CCID_CODE> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty cachedFragment}">  
<c:set var="cachedFragment" scope="application">  
...  
</c:set></c:if>

缓存页面片段用下列语句输出结果: 

<CCID_NOBR>

<CCID_CODE>${applicationScope.cachedFragment}

  

当缓存片段需要被每一个请求所定制的时候,到底发生了什么?

例如,如果希望包含一个计数器,需要缓存两个片段:

<CCID_NOBR>

<CCID_CODE><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${sessionScope.counter == null}">  
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<c:if test="${empty cachedFragment1}">  
<c:set var="cachedFragment1" scope="application">  
...  
</c:set></c:if><c:if test="${empty cachedFragment2}">  
<c:set var="cachedFragment2" scope="application">  
...  
</c:set></c:if>    

可以使用下面语句输出缓存内容:

<CCID_NOBR>

<CCID_CODE>${cachedFragment1} ${counter} ${cachedFragment2}

 

通过专门的标签库的帮助,需要定制的页面片段的缓存变得异常容易了。上面已经提及,缓存内容可以被开始标签(<jc:cache>)和结尾标签(</jc:cache>)封装起来。而每一个定制可以使用另一个标签(<jc:dynamic expr="..."/>)输出一个JSP表达式(${...})来表现。动态内容用JSP表达式缓存并在每一次缓存内容被输出时赋值。在下面的部分可以看到这是如何实现的。Counter.jsp缓存了一个包含计数器的页面片段,当每一次用户刷新这个页面的时候计数器会自动+1。

<CCID_NOBR>

<CCID_CODE><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %>
<c:if test="${sessionScope.counter == null}">  
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<jc:cache id="cachedFragmentWithCounter">  
...
<jc:dynamic expr="sessionScope.counter"/>  
...
</jc:cache>

JSP 变量易于使用,对于简单的Web apps,这是一个不错的内容缓存方案。然而,如果应用程序产生大量的动态内容,没有对缓存大小的控制无疑是一个问题。一种专用的缓存框架结构能够提供一个更加有力的方案,允许对缓存的监视,限制缓存大小,控制缓存策略,等等……

使用JSP 2.0表达式语言API

JSP容器(例如Tomcat)对应用EL API的JSP页面中的表达式予以赋值,并且可以被Java代码所使用。这允许在Web页面外应用JSP EL作开发,例如,对XML文件、基于文本的资源以及自定义脚本。当需要控制何时对Web页面中的表达式进行赋值或者书写与之相关的表达式时,EL API同样是有用的。例如,缓存页面片段可以包含自定义JSP表达式,并且当每一次缓存内容被输出时,EL API将用来给这些表达式赋值或者重新赋值。

文章提供了一个例子程序(参见文末资源部分),这个应用程序包含了一个Java 类(JspUtils)和类中包含一个方法eval(),这个方法有三个参数:JSP表达式、表达式的期望类型和一个JSP context对象。Eval()方法从JSP context中取得ExpressionEvaluator并且调用evaluate()方法,通过表达式、表达式的期望类型、和一个从JSP congtext中获得的变量。JspUtils.eval()方法返回表达式的值。

<CCID_NOBR>

<CCID_CODE>package com.devsphere.articles.jspcache;  
import javax.servlet.jsp.JspContext;  
import javax.servlet.jsp.JspException;  
import javax.servlet.jsp.PageContext;  
import javax.servlet.jsp.el.ELException;  
import javax.servlet.jsp.el.ExpressionEvaluator;  
import java.io.IOException;public class JspUtils
{  
public static Object eval(  String expr, Class type, JspContext jspContext)  
throws JspException
{  
try
{  
if (expr.indexOf("${") == -1)  return expr;  
ExpressionEvaluator evaluator= jspContext.getExpressionEvaluator();  
return evaluator.evaluate(expr, type,  
jspContext.getVariableResolver(), null);  
} catch (ELException e)
{  
throw new JspException(e);  
}  
}  
...
}

注意:JspUtils.eval()主要封装了标准的ExpressionEvaluator。如果expr不包含${,JSP EL API不被调用,因为没有JSP表达式。

创建标签库描述符(TLD)文件

JSP标签库需要一个标签库描述符(TLD)文件来自定义标签的命名,它们的属性,以及操作该标签的Java类。jspcache.tld描述了两个自定义标签,<jc:cache>拥有两个属性:缓存页面片段的id和JSP scope—JSP页面总需要被储存的内容范围。<jc:dynamic>只有一个属性,即JSP表达式必须在每一次缓存片段被输出时被赋值。TLD文件将这两个自定义标签映射到CacheTag和DynamicTag类,如下所示:

<CCID_NOBR>

<CCID_CODE><?xml version="1.0" encoding="UTF-8" ?><taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2eeweb-jsptaglibrary_2_0.xsd"  version="2.0">  
<tlib-version>1.0</tlib-version>  
<short-name>jc</short-name>  
<uri>http://devsphere.com/articles/jspcache</uri>  
<tag>  
<name>cache</name>  
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>  
<body-content>scriptless</body-content>  
<attribute>  <name>id</name>  <required>true</required>  
<rtexprvalue>true</rtexprvalue>  </attribute>  <attribute>  
<name>scope</name>  <required>false</required>  
<rtexprvalue>false</rtexprvalue>  </attribute>  
</tag>  <tag>  <name>dynamic</name>  <tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class> <body-content>empty</body-content>  <attribute>  
<name>expr</name>  <required>true</required>  
<rtexprvalue>false</rtexprvalue>  </attribute>  
</tag></taglib>

TLD文件包含在Web应用描述符文件(web.xml)中,这五个文件同样包含一个初始参数指出cache是否可用。

<CCID_NOBR>

<CCID_CODE><?xml version="1.0" encoding="ISO-8859-1"?><web-app xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd" version="2.4">  <context-param>  <param-name>com.devsphere.articles.jspcache.enabled</param-name><param-value>true</param-value>  </context-param><jsp-config>  <taglib>  
<taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri>  
<taglib-location>/WEB-INF/jspcache.tld</taglib-location>  
</taglib>  </jsp-config></web-app>

理解<jc:cache>的工作机理

JSP容器为JSP页面中的每一个<jc:cache>标签创建一个 CacheTag实例,来对其处理。JSP容器负责调用setJsp()、setParent()和setJspBody()方法,这是CacheTag 类从SimpleTagSupport继承而来。JSP容器同事还为所操作标签的每一个属性调用setter方法。SetId()和setScope() 方法存储属性值到私有域,这个值已经用CacheTag()构造函数用缺省值初始化。

<CCID_NOBR>

<CCID_CODE>package com.devsphere.articles.jspcache;  
import javax.servlet.ServletContext;  
import javax.servlet.jsp.JspContext;  
import javax.servlet.jsp.JspException;  
import javax.servlet.jsp.PageContext;  
import javax.servlet.jsp.tagext.SimpleTagSupport;  
import java.io.IOException;import java.io.StringWriter;  
public class CacheTag extends SimpleTagSupport
{  
public static final String CACHE_ENABLED  =
"com.devsphere.articles.jspcache.enabled";  
private String id;  private int scope;  
private boolean cacheEnabled;  public CacheTag()
{
id = null;scope = PageContext.APPLICATION_SCOPE;  
}public void setId(String id)
{
this.id = id;  
}  
public void setScope(String scope)
{
this.scope = JspUtils.checkScope(scope);  
}
...
}
setScope()方法调用JspUtils.checkScope()来校验已经
从String转换为int类型的scope的属性值。  
...public class JspUtils
{  
...  
public static int checkScope(String scope)
{  
if ("page".equalsIgnoreCase(scope))  
return PageContext.PAGE_SCOPE;  
else if ("request".equalsIgnoreCase(scope))  
return PageContext.REQUEST_SCOPE;  
else if ("session".equalsIgnoreCase(scope))  
return PageContext.SESSION_SCOPE;  
else if ("application".equalsIgnoreCase(scope))  
return PageContext.APPLICATION_SCOPE;  
else  throw new IllegalArgumentException
(  "Invalid scope: " + scope);  }}

一旦CacheTag实例准备对标签进行操作,JSP容器调用doTag()方法,用getJspContext()来获得JSP context。这个对象被造型为PageContext,从而可以调用getServletContext()方法。servlet context用来获取初始化参数的值,这个值标明缓存机制是否被启用。如果缓存被启用,doTag()尝试使用id和scope属性值来获得缓存页面片段。如果页面片段还没有被缓存,doTag()使用getJspBody().invoke()来执行由<jc:cache>和</jc:cache>封装的JSP代码。由JSP body产生的输出结果缓冲在StringWriter并且被toStirng()方法获得。这样,doTag()调用JSP context的setAttribute()方法新建一个JSP变量,这个变量控制可能包含JSP表达式(${…})的缓存内容。这些表达式在用 jspContext.getOut().print()输出内容前,被JspUtils.eval()赋值。只有当缓存被启用的时候,这些行为才发生。否则,doTag()只是通过getJspBody().invoke(null)执行JSP body并且输出结果不被缓存。

<CCID_NOBR>

<CCID_CODE>...
public class CacheTag extends SimpleTagSupport
{
...
public void doTag() throws JspException, IOException
{  
JspContext jspContext = getJspContext();  
ServletContext application  = ((PageContext)
jspContext).getServletContext(); String cacheEnabledParam= application.getInitParameter(CACHE_ENABLED);  cacheEnabled = cacheEnabledParam != null && cacheEnabledParam.equals("true");  
if (cacheEnabled)
{  
String cachedOutput= (String) jspContext.getAttribute(id, scope);  
if (cachedOutput == null)
{
StringWriter buffer = new StringWriter();  
getJspBody().invoke(buffer);  
cachedOutput = buffer.toString();  
jspContext.setAttribute(id, cachedOutput, scope);  
}      
String evaluatedOutput = (String)
JspUtils.eval(  cachedOutput, String.class, jspContext);  
jspContext.getOut().print(evaluatedOutput);  
}
else  getJspBody().invoke(null);  
}  
...
}

注意一个单独的JspUtils.eval()调用给所有的${…} 表达式赋值。因为一个包含了大量的${…}结构的text也是一个表达式。每一个缓存片段都可以被当作一个复杂的JSP表达式来进行处理。     IsCacheEnabled()方法返回cacheEnabled的值,这个值已经被doTag()初始化。

<CCID_NOBR>

<CCID_CODE>IsCacheEnabled()方法返回cacheEnabled的值,这个值已经被doTag()初始化。
...public class CacheTag extends SimpleTagSupport
{
...  
public boolean isCacheEnabled() {  return cacheEnabled;  
}
}

<jc:cache>标签允许页面开发者自主选择缓存页面片段的ID。这使得缓存一个页面片段可以被多个JSP页面共享,当需要重用JSP代码时,这是很有用处的。但是仍然需要一些命名协议来避免可能的冲突。通过修改CacheTag类来在自动 ID内部包含URL可以避免这种副作用。 

理解<jc:dynamic>在做什么

每一个<jc:dynamic>被一个DynamicTag类的实例处理, setExpr()方法将expr属性值存储到一个私有域。DoTag()方法创建JSP表达式,在expr属性值加上${前缀和}后缀。然后, doTag()使用findAncestorWithClass()来查找含有<jc:dynamic>标签元素的<jc:cache>的 CacheTag handler。如果没有查找到或者缓存被禁用,JSP表达式被JspUtils.eval()赋值并且值被输出。否则,doTag()输出无值表达式。

<CCID_NOBR>

<CCID_CODE>package com.devsphere.articles.jspcache;  
import javax.servlet.jsp.JspException;  
import javax.servlet.jsp.tagext.SimpleTagSupport;  
import java.io.IOException;  
public class DynamicTag extends SimpleTagSupport
{  
private String expr;  public void setExpr(String expr)
{this.expr = expr;  
}  
public void doTag() throws JspException, IOException
{
String output = "${" + expr + "}";  
CacheTag ancestor = (CacheTag) findAncestorWithClass
(  this, CacheTag.class);  
if (ancestor == null || !ancestor.isCacheEnabled())  
output = (String) JspUtils.eval
(  output, String.class, getJspContext());  
getJspContext().getOut().print(output);  
}
}

分析上面的代码,大家可以注意到<jc:cache>和<jc:dynamic>合作来实现一个尽可能有效率的方案。如果缓存可用,页面片段和由<jc:dynamic>生成并被CacheTag赋值的JSP表达式一起放入缓冲器。如果缓存被禁用,缓冲变得没有意义,<jc:cache>只是执行其JSP body部分,而让DynamicTag给JSP表达式赋值。禁用缓存有时候是必要的,特别是在开发过程期间出现内容的改变和JSP页面被重新编译的时候。当然,在开发完毕的成品环境中缓存必须被启用。

总结

内容缓存是一种非常易用的改善Web应用性能的方法。这篇文章集中讨论了使用JSP 表达式语言来为每一个用户或者请求定制缓存内容。贯穿全文的简单介绍的标签库适合小型Web apps并且可以提升中等应用的性能。对于开发大型企业级应用,则应当考虑使用支持更好的缓存机制的框架结构,而不仅仅局限于使用JSP变量。

攀登者IT技术交流总群:141098671(500人) 攀登者IT技术交流贰群:168051867(500人)|| 攀登者IT【.Net】精英:172663374 || 攀登者IT【Java】精英:216931633||攀登者IT技术长沙分群:222932346(只加在长沙工作的朋友)。欢迎你的加入进行技术探讨!加群时请写好技术方向,否则不批准!

转载于:https://www.cnblogs.com/liyumei/articles/2513681.html

深入讲解JSP 2.0下的动态内容缓存技术相关推荐

  1. aws lambda使用_使用AWS Lambda,S3和AWS CloudFront进行动态内容缓存

    aws lambda使用 快速提供内容对于任何网站或应用程序具有更好的客户体验都是必不可少的. 如果您将网站或应用程序托管在AWS Cloud中,那么无论从何处访问应用程序,都可以以较低的延迟快速提供 ...

  2. 使用AWS Lambda,S3和AWS CloudFront进行动态内容缓存

    快速提供内容对于任何网站或应用程序具有更好的客户体验至关重要. 如果您将网站或应用程序托管在AWS Cloud中,那么无论从何处访问应用程序,都可以以较低的延迟快速提供内容. AWS提供了CloudF ...

  3. .Net 4.0 之 Dynamic 动态类型

    本文主要旨在与网友分享.Net4.0的Dynamic 对Duck Type 的支持.     一..net4.0主要新特性 .Net4.0在.Net3.5基础上新增的主要特性有:可选参数.命名参数和D ...

  4. js动态改变下拉菜单内容示例 .

    <HTML><HEAD><TITLE>动态改变下拉菜单内容示例</TITLE></HEAD><SCRIPT LANGUAGE=java ...

  5. asp.net 2.0下嵌套masterpage页的可视化编辑

    相信大家都用过asp.net 2.0下的嵌套的masterpage功能,该功能很强大,可以让用户先制作出模版页后,然后很容易地套用到其他页面中去.而且模版页还可以嵌套的,但嵌套后的模版页,我们会发觉有 ...

  6. ASP.NET Core 3.0中使用动态控制器路由

    原文:Dynamic controller routing in ASP.NET Core 3.0 作者:Filip W 译文:https://www.cnblogs.com/lwqlun/p/114 ...

  7. java ee jsp_EE JSP:使用自定义标签库生成动态内容

    java ee jsp 在Web应用程序中开发View层时,您希望尽量不要重复JSP文件中的内容. JSP spec API允许您通过使用标记库来减少这种重复. 定制JSP标记是用户定义的xml标记元 ...

  8. EE JSP:使用自定义标签库生成动态内容

    在Web应用程序中开发View层时,您希望尽量不要重复JSP文件中的内容. JSP spec API允许您通过使用标记库来减少这种重复. 定制JSP标记是用户定义的xml标记元素,您可以将其插入JSP ...

  9. 图解WinCE6.0下的内核驱动和用户驱动

    图解WinCE6.0下的内核驱动和用户驱动 在<WinCE驱动程序的分类>中曾提到,WinCE6.0的流驱动既可以加载到内核态也可以加载到用户态.下面通过一组图片简单说明一下这两种驱动的关 ...

最新文章

  1. 一个RESTful+MySQL程序
  2. java arraylist 实现原理_Java进阶--深入理解ArrayList实现原理
  3. Minst 0-9特征迭代次数曲线表达式
  4. 神策 2021 数据驱动大会「积分有礼」榜单今日揭晓!快来看看你排第几?
  5. dart系列之:在dart中使用packages
  6. 中小企业的软件需求问题
  7. 50岁,投资理财方面接触较少,手上有280万,该如何操作?
  8. sonar扫的是class还是Java_如何在同一个项目中为java和javascript配置Sonarqube扫描程序...
  9. 电脑键盘打字手法_电脑快速打字技巧,送个每一位初学者
  10. 使用浏览器测试Webservers服务时出现提示“The test form is only available for requests from the local machine.”的解决办法
  11. php练手的项目,learnphp.beginmaker.com
  12. mysql 自动复制_MySQL复制 自动监控脚本-阿里云开发者社区
  13. VS添加lib库文件步骤
  14. 经典Java入门视频教程免费下载
  15. html图片滚动红点_程序员最爱的小红点更新了!史上最薄的钛制ThinkPad发布
  16. Chrome扩展开发总结之浏览器书签获取
  17. UltraLAB台式图形工作站(地球最快~超级图形工作站Alpha720介绍)
  18. 【H5】 svg的 defs用法 渐变
  19. Web前端开发技术课程大作业:基于HTML+CSS+JavaScript实现校园主题-萍乡田家炳中学校网站(1页)
  20. 阿里云是一个什么样的企业,为什么能成为行业第一

热门文章

  1. android 原生分享界面_这些技巧和习惯,让你的原生 Android 更好用(上篇)
  2. shellwhile比较_[Shell] if、for、while流程语句以及整数字符串判断比较的实例详解...
  3. python api 示例_Python简单接口测试实例
  4. 浸会大学计算机专业硕士排名2019,2019软科世界大学学术排名香港浸会大学排名第701-800...
  5. python敏感词过滤代码简单_大型企业都在用,Python实现敏感词过滤
  6. php time 循环不变,PHP为系统调用的脚本设置超时,set_time_limit不起作用
  7. 兰大202005批次计算机基础,【每日一校】2020年兰州大学 各批次录取人数与分数线统计...
  8. 会自学的四足机器人,3.5个小时学会走路!
  9. verilog7人表决电路设计
  10. 高斯消元法解方程matlab程序,用高斯消元法解线性方程组 的MATLAB程序