加入收藏 | 设为首页 | 会员中心 | 我要投稿 聊城站长网 (https://www.0635zz.com/)- 智能语音交互、行业智能、AI应用、云计算、5G!
当前位置: 首页 > 教程 > 正文

JSP 2.0下的动态内容缓存剖析讲解

发布时间:2023-05-29 13:31:39 所属栏目:教程 来源:
导读:在web应用中,内容缓存是最普通的优化技术之一,并且能够很容易地实现。例如,可以使用一个自定义地jsp标签——我们将之命名为<jc:cache>——由<jc:cache>和</jc:cache>将每一个需要被缓存
在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变量正如下例所示:

  <%@ 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>
 
  缓存页面片段用下列语句输出结果:
 
  ${applicationscope.cachedfragment}

  当缓存片段需要被每一个请求所定制的时候,到底发生了什么?例如,如果希望包含一个计数器,需要缓存两个片段:
 
  <%@ 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>

  可以使用下面语句输出缓存内容:
 
  ${cachedfragment1} ${counter} ${cachedfragment2}

  通过专门的标签库的帮助,需要定制的页面片段的缓存变得异常容易了。上面已经提及,缓存内容可以被开始标签(<jc:cache>)和结尾标签(</jc:cache>)封装起来。而每一个定制可以使用另一个标签(<jc:dynamic expr="..."/>)输出一个jsp表达式(${...})来表现。动态内容用jsp表达式缓存并在每一次缓存内容被输出时赋值。在下面的部分可以看到这是如何实现的。counter.jsp缓存了一个包含计数器的页面片段,当每一次用户刷新这个页面的时候计数器会自动+1。
 
  <%@ 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()方法返回表达式的值。
 
  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类,如下所示:
 
  <?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/j2ee web-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是否可用。
 
  <?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()构造函数用缺省值初始化。
 
  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并且输出结果不被缓存。
 
  ...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()初始化。
 
  ...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()输出无值表达式。

  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页面被重新编译的时候。当然,在开发完毕的成品环境中缓存必须被启用。
 
 

(编辑:聊城站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!