Detaillierte Erläuterung des dynamischen Inhalts-Caching unter JSP 2.0
Autor:Eve Cole
Aktualisierungszeit:2009-07-03 16:56:34
Content-Caching ist eine der häufigsten Optimierungstechniken in Webanwendungen und lässt sich einfach implementieren. Sie können beispielsweise ein benutzerdefiniertes JSP-Tag – nennen wir es <jc:cache> – mit <jc:cache> und </jc:cache> verwenden, um jedes Seitenfragment zu kapseln, das zwischengespeichert werden muss. Jedes benutzerdefinierte Tag kann steuern, wann der darin enthaltene Teil (d. h. das vorgefertigte Seitenfragment) ausgeführt wird, und die dynamischen Ausgabeergebnisse können erfasst werden. Das <jc:cache>-Tag ermöglicht es einem JSP-Container (z. B. Tomcat), Inhalte nur einmal als anwendungsweite JSP-Variablen zu generieren, um jedes Cache-Fragment zu speichern. Jedes Mal, wenn die JSP-Seite ausgeführt wird, lädt das benutzerdefinierte Tag das zwischengespeicherte Seitenfragment, ohne dass der JSP-Code erneut ausgeführt werden muss, um die Ausgabe zu generieren. Im Rahmen des Jakarta-Projekts wurde die Tag-Bibliothek mit dieser Technologie entwickelt. Es funktioniert gut, wenn der zwischengespeicherte Inhalt nicht für jeden Benutzer oder jede Anfrage angepasst werden muss.
Dieser Artikel verbessert die oben beschriebene Technik durch die Verwendung von JSP 2.0 Expression Language (EL), wodurch JSP-Seiten zwischengespeicherte Inhalte für jede Anfrage und jeden Benutzer anpassen können. Cache-Seitenfragmente können JSP-Ausdrücke enthalten, die nicht vom JSP-Container ausgewertet werden. Die Werte dieser Ausdrücke werden bei jeder Ausführung der Seite durch benutzerdefinierte Tags bestimmt. Daher wird die Erstellung dynamischer Inhalte optimiert, zwischengespeicherte Fragmente können jedoch Teile des Inhalts enthalten, der von jeder Anforderung mithilfe der nativen JSP-Ausdruckssprache generiert wird. Mit Hilfe der JSP 2.0 EL API können Java-Entwickler dies mithilfe von Ausdruckssprachen ermöglichen.
Inhalts-Caching vs. Daten-Caching Inhalts-Caching ist nicht die einzige Option. Beispielsweise können auch aus einer Datenbank extrahierte Daten zwischengespeichert werden. Tatsächlich kann das Zwischenspeichern von Daten effizienter sein, da die gespeicherten Informationen kein HTML-Markup enthalten und weniger Speicher benötigen. In vielen Fällen ist In-Memory-Caching jedoch einfacher zu implementieren. Angenommen, in einem bestimmten Fall besteht eine Anwendung aus einer großen Anzahl von Transaktionsobjekten, belegt wichtige CPU-Ressourcen, generiert komplexe Daten und verwendet JSP-Seiten zur Darstellung der Daten. Alles funktionierte einwandfrei, bis eines Tages die Auslastung des Servers plötzlich anstieg und eine Notlösung erforderlich war. Derzeit ist die Einrichtung einer Cache-Schicht zwischen dem Transaktionsobjekt und der Präsentationsschicht eine sehr gute und effektive Lösung. Aber JSP-Seiten, die dynamische Inhalte zwischenspeichern, müssen sehr schnell und reibungslos geändert werden. Im Vergleich zur einfachen JSP-Seitenbearbeitung erfordern Änderungen an der Geschäftslogik der Anwendung normalerweise mehr Arbeit und Tests. Wenn eine Seite außerdem Informationen aus mehreren zusammengesetzten Quellen zusammenfasst, sind auf der Webebene nur wenige Änderungen erforderlich. Das Problem besteht darin, dass Cache-Speicherplatz freigegeben werden muss, wenn zwischengespeicherte Informationen veraltet sind, und das Transaktionsobjekt sollte wissen, wann dies geschieht. Bei der Entscheidung, Content-Caching oder Daten-Caching oder andere Optimierungstechniken zu implementieren, müssen jedoch viele Faktoren berücksichtigt werden, und manchmal gibt es spezielle Anforderungen an das zu entwickelnde Programm.
Daten-Caching und Inhalts-Caching schließen sich nicht unbedingt gegenseitig aus, sie können auch zusammen verwendet werden. Beispielsweise werden in einer datenbankgesteuerten Anwendung die aus der Datenbank extrahierten Daten und der HTML-Code, der die Daten darstellt, separat zwischengespeichert. Dies ähnelt in gewisser Weise der Verwendung von JSP zum Generieren von Vorlagen in Echtzeit. Die in diesem Artikel besprochenen EL-basierten API-Techniken veranschaulichen, wie JSP EL zum Laden von Daten in Rendering-Vorlagen verwendet wird.
Dynamische Inhalte mithilfe von JSP-Variablen zwischenspeichern Wenn Sie einen Caching-Mechanismus implementieren, benötigen Sie eine Methode zum Speichern von Cache-Objekten. In diesem Artikel handelt es sich um Objekte vom Typ String. Eine Möglichkeit besteht darin, ein Objekt-Caching-Framework zu verwenden oder Java-Maps zu verwenden, um ein benutzerdefiniertes Caching-Schema zu implementieren. JSP verfügt bereits über sogenannte „bereichsbezogene Attribute“ oder „JSP-Variablen“, um eine ID-Objekt-Zuordnung bereitzustellen, was für den Caching-Mechanismus erforderlich ist. Für die Verwendung von Seiten- oder Anforderungsbereichen ist dies nicht sinnvoll, während dies im Anwendungsbereich ein guter Ort zum Speichern zwischengespeicherter Inhalte ist, da diese von allen Benutzern und Seiten gemeinsam genutzt werden. Der Sitzungsbereich kann auch verwendet werden, wenn für jeden Benutzer ein separates Caching erforderlich ist, dies ist jedoch nicht sehr effizient. Die JSTL-Tag-Bibliothek kann damit zum Zwischenspeichern von Inhalten verwendet werden, indem JSP-Variablen verwendet werden, wie im folgenden Beispiel gezeigt:
<%@ 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>
Das zwischengespeicherte Seitenfragment gibt die Ergebnisse mit der folgenden Anweisung aus:
${applicationScope.cachedFragment}
Was passiert, wenn das Cache-Fragment für jede Anfrage angepasst werden muss? Wenn Sie beispielsweise einen Zähler einschließen möchten, müssen Sie zwei Fragmente zwischenspeichern:
<%@ 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>
Mit der folgenden Anweisung können Sie den Cache-Inhalt ausgeben:
${cachedFragment1} ${counter} ${cachedFragment2}
Mit Hilfe einer speziellen Tag-Bibliothek wird das Zwischenspeichern von Seitenfragmenten, die angepasst werden müssen, extrem einfach. Wie oben erwähnt, können zwischengespeicherte Inhalte durch öffnende Tags (<jc:cache>) und schließende Tags (</jc:cache>) eingeschlossen werden. Jede Anpassung kann mithilfe eines anderen Tags (<jc:dynamic expr="..."/>) ausgedrückt werden, um einen JSP-Ausdruck (${...}) auszugeben. Dynamische Inhalte werden mithilfe von JSP-Ausdrücken zwischengespeichert und jedes Mal zugewiesen, wenn der zwischengespeicherte Inhalt ausgegeben wird. Wie das gelingt, sehen Sie im folgenden Abschnitt. Counter.jsp speichert ein Seitenfragment zwischen, das einen Zähler enthält. Der Zähler wird jedes Mal, wenn der Benutzer die Seite aktualisiert, automatisch um 1 erhöht.
<%@ 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-Variablen sind einfach zu verwenden und eine gute Inhalts-Caching-Lösung für einfache Web-Apps. Wenn die Anwendung jedoch viele dynamische Inhalte generiert, ist es definitiv ein Problem, keine Kontrolle über die Cache-Größe zu haben. Ein dediziertes Cache-Framework kann eine leistungsfähigere Lösung bieten und die Cache-Überwachung, die Begrenzung der Cache-Größe, die Steuerung der Cache-Richtlinie usw. ermöglichen.
Verwendung der JSP 2.0 Expression Language API
JSP-Container (z. B. Tomcat) werten Ausdrücke in JSP-Seiten mithilfe der EL-API aus und können von Java-Code verwendet werden. Dies ermöglicht die Entwicklung außerhalb von Webseiten mithilfe von JSP EL, beispielsweise für XML-Dateien, textbasierte Ressourcen und benutzerdefinierte Skripts. Die EL-API ist auch nützlich, wenn Sie steuern müssen, wann Ausdrücke auf einer Webseite zugewiesen oder damit verknüpfte Ausdrücke geschrieben werden. Beispielsweise können zwischengespeicherte Seitenfragmente benutzerdefinierte JSP-Ausdrücke enthalten, und die EL-API wird verwendet, um diese Ausdrücke jedes Mal zuzuweisen oder neu auszuwerten, wenn der zwischengespeicherte Inhalt ausgegeben wird.
Der Artikel enthält ein Beispielprogramm (siehe Abschnitt „Ressourcen“ am Ende des Artikels). Diese Anwendung enthält eine Java-Klasse (JspUtils) und eine Methode eval() in der Klasse. Diese Methode verfügt über drei Parameter: JSP-Ausdruck, den erwarteten Typ des Ausdrucks und ein JSP-Kontextobjekt. Die Eval()-Methode ruft den ExpressionEvaluator aus dem JSP-Kontext ab und ruft die evaluieren()-Methode auf, wobei sie den Ausdruck, den erwarteten Typ des Ausdrucks und eine aus dem JSP-Kontext erhaltene Variable übergibt. Die Methode JspUtils.eval() gibt den Wert des Ausdrucks zurück.
Paket 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 {
öffentliches statisches Objekt eval(
String-Ausdruck, Klassentyp, JspContext (jspContext)
wirft JspException {
versuchen {
if (expr.indexOf("${") == -1)
Rückgabeausdruck;
ExpressionEvaluator-Evaluator
= jspContext.getExpressionEvaluator();
return evaluator.evaluate(expr, type,
jspContext.getVariableResolver(), null);
} Catch (ELException e) {
throw new JspException(e);
}
}
...}
Hinweis: JspUtils.eval() kapselt hauptsächlich den Standard-ExpressionEvaluator. Wenn expr ${ nicht enthält, wird die JSP EL API nicht aufgerufen, da kein JSP-Ausdruck vorhanden ist.
Erstellen einer TLD-Datei (Tag Library Descriptor) Eine JSP-Tag-Bibliothek erfordert eine TLD-Datei (Tag Library Descriptor), um die Benennung von Tags, ihren Attributen und den Java-Klassen, die mit dem Tag arbeiten, anzupassen. jspcache.tld beschreibt zwei benutzerdefinierte Tags. <jc:cache> hat zwei Attribute: die ID des zwischengespeicherten Seitenfragments und den JSP-Bereich – den Inhaltsbereich der JSP-Seite, der immer gespeichert werden muss. <jc:dynamic> hat nur ein Attribut, den JSP-Ausdruck, der jedes Mal ausgewertet werden muss, wenn das Cache-Fragment ausgegeben wird. Die TLD-Datei ordnet diese beiden benutzerdefinierten Tags wie folgt den Klassen CacheTag und DynamicTag zu:
<?xml version="1.0" binding="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>skriptlos</body-content>
<Attribut>
<name>id</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<Attribut>
<name>Bereich</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<Tag>
<name>dynamisch</name>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>leer</body-content>
<Attribut>
<name>expr</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag></taglib>
Die TLD-Datei ist in der Webanwendungsdeskriptordatei (web.xml) enthalten. Diese fünf Dateien enthalten auch einen Anfangsparameter, der angibt, ob der Cache verfügbar ist.
<?xml version="1.0" binding="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">
<Kontextparameter>
<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>
Verstehen Sie den Arbeitsmechanismus von <jc:cache>. Der JSP-Container erstellt eine CacheTag-Instanz für jedes <jc:cache>-Tag auf der JSP-Seite, um es zu verarbeiten. Der JSP-Container ist für den Aufruf der Methoden setJsp (), setParent () und setJspBody () verantwortlich, bei denen es sich um die von SimpleTagSupport geerbte CacheTag-Klasse handelt. Der JSP-Container ruft außerdem die Setter-Methode für jedes Attribut des manipulierten Tags auf. Die Methoden SetId() und setScope() speichern den Attributwert in einem privaten Feld. Dieser Wert wurde mithilfe des CacheTag()-Konstruktors mit dem Standardwert initialisiert.
Paket 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;
Die öffentliche Klasse CacheTag erweitert SimpleTagSupport {
öffentliche statische endgültige Zeichenfolge CACHE_ENABLED
= "com.devsphere.articles.jspcache.enabled";
private String-ID;
privater int-Bereich;
privater boolescher 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);
}
...}
Die Methode setScope() ruft JspUtils.checkScope() auf, um den Eigenschaftswert des Bereichs zu überprüfen, der vom Typ String in den Typ int konvertiert wurde.
...öffentliche Klasse 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;
anders
wirf eine neue IllegalArgumentException(
„Ungültiger Bereich: „ + Bereich);
}}
Sobald die CacheTag-Instanz bereit ist, mit dem Tag zu arbeiten, ruft der JSP-Container die Methode doTag() auf und ruft den JSP-Kontext mit getJspContext() ab. Dieses Objekt wird als PageContext umgewandelt, sodass die Methode getServletContext() aufgerufen werden kann. Der Servlet-Kontext wird verwendet, um den Wert des Initialisierungsparameters abzurufen, der angibt, ob der Caching-Mechanismus aktiviert ist. Wenn das Caching aktiviert ist, versucht doTag(), das zwischengespeicherte Seitenfragment mithilfe der Attributwerte „id“ und „scope“ abzurufen. Wenn das Seitenfragment nicht zwischengespeichert wurde, verwendet doTag() getJspBody().invoke(), um den von <jc:cache> und </jc:cache> gekapselten JSP-Code auszuführen. Die vom JSP-Body generierte Ausgabe wird in einem StringWriter gepuffert und von der toStirng()-Methode abgerufen. Auf diese Weise ruft doTag() die Methode setAttribute() des JSP-Kontexts auf, um eine neue JSP-Variable zu erstellen. Diese Variable steuert den Cache-Inhalt, der JSP-Ausdrücke (${…}) enthalten kann. Diese Ausdrücke werden von JspUtils.eval() zugewiesen, bevor Inhalte mit jspContext.getOut().print() ausgegeben werden. Dieses Verhalten tritt nur auf, wenn Caching aktiviert ist. Andernfalls führt doTag() einfach den JSP-Körper über getJspBody().invoke(null) aus und das Ausgabeergebnis wird nicht zwischengespeichert.
...öffentliche Klasse CacheTag erweitert SimpleTagSupport {
...
public void doTag() wirft JspException, IOException {
JspContext jspContext = getJspContext();
ServletContext-Anwendung
= ((PageContext) jspContext).getServletContext();
String-cacheEnabledParam
= application.getInitParameter(CACHE_ENABLED);
CacheEnabled = CacheEnabledParam! = Null
&& cacheEnabledParam.equals("true");
if (cacheEnabled) {
String zwischengespeicherter Ausgabe
= (String) jspContext.getAttribute(id, Scope);
if (cachedOutput == null) {
StringWriter buffer = new StringWriter();
getJspBody().invoke(buffer);
CachedOutput = buffer.toString();
jspContext.setAttribute(id,cachedOutput,scope);
} String ausgewertetOutput = (String) JspUtils.eval(
zwischengespeicherterAusgabe, String.class, jspContext);
jspContext.getOut().print(evaluatedOutput);
} anders
getJspBody().invoke(null);
}
...}
Beachten Sie, dass ein einzelner JspUtils.eval()-Aufruf alle ${…}-Ausdrücke auswertet. Denn auch ein Text, der viele ${...}-Strukturen enthält, ist ein Ausdruck. Jedes Cache-Fragment kann als komplexer JSP-Ausdruck verarbeitet werden.
Die Methode IsCacheEnabled() gibt den Wert von cacheEnabled zurück, der von doTag() initialisiert wurde.
...öffentliche Klasse CacheTag erweitert SimpleTagSupport {
... public boolean isCacheEnabled() {
return cacheEnabled;
}}
Mit dem <jc:cache>-Tag können Seitenentwickler die ID der zwischengespeicherten Seitenfragmente auswählen. Dadurch können zwischengespeicherte Seitenfragmente von mehreren JSP-Seiten gemeinsam genutzt werden, was bei der Wiederverwendung von JSP-Code nützlich ist. Es ist jedoch noch ein Benennungsprotokoll erforderlich, um mögliche Konflikte zu vermeiden. Dieser Nebeneffekt kann vermieden werden, indem die CacheTag-Klasse so geändert wird, dass die URL in die automatische ID aufgenommen wird.
Verstehen, was <jc:dynamic> tut. Jedes <jc:dynamic> wird von einer Instanz der DynamicTag-Klasse verarbeitet, und die setExpr()-Methode speichert den expr-Attributwert in einem privaten Feld. Die DoTag()-Methode erstellt einen JSP-Ausdruck und fügt dem expr-Attributwert das Präfix ${ und das Suffix } hinzu. Anschließend verwendet doTag() findAncestorWithClass(), um den CacheTag-Handler von <jc:cache> zu finden, der das Tag-Element <jc:dynamic> enthält. Wenn nicht gefunden oder das Caching deaktiviert ist, wird der JSP-Ausdruck mit JspUtils.eval() ausgewertet und der Wert ausgegeben. Andernfalls gibt doTag() einen wertlosen Ausdruck aus.
Paket com.devsphere.articles.jspcache;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
Die öffentliche Klasse DynamicTag erweitert SimpleTagSupport {
privater String expr;
public void setExpr(String expr) {
this.expr = expr;
} public void doTag() löst JspException, IOException {
String-Ausgabe = "${" + expr + "}";
CacheTag ancestor = (CacheTag) findAncestorWithClass(
this, CacheTag.class);
if (ancestor == null || !ancestor.isCacheEnabled())
Ausgabe = (String) JspUtils.eval(
Ausgabe, String.class, getJspContext());
getJspContext().getOut().print(output);
}}
Wenn Sie den obigen Code analysieren, können Sie feststellen, dass <jc:cache> und <jc:dynamic> zusammenarbeiten, um eine möglichst effiziente Lösung zu erzielen. Wenn Cache verfügbar ist, wird das Seitenfragment zusammen mit dem von <jc:dynamic> generierten JSP-Ausdruck in den Puffer gestellt und dem CacheTag-Wert zugewiesen. Wenn das Caching deaktiviert ist, wird das Caching bedeutungslos und <jc:cache> führt einfach seinen JSP-Körperteil aus, sodass DynamicTag dem JSP-Ausdruck Werte zuweisen kann. Manchmal ist es notwendig, das Caching zu deaktivieren, insbesondere während des Entwicklungsprozesses, wenn sich Inhalte ändern und JSP-Seiten neu kompiliert werden. Natürlich muss das Caching nach der Entwicklung in der Produktionsumgebung aktiviert werden.
Zusammenfassen
Für die Entwicklung großer Unternehmensanwendungen sollten Sie die Verwendung eines Frameworks in Betracht ziehen, das bessere Caching-Mechanismen unterstützt, anstatt nur JSP-Variablen zu verwenden. Es ist jedoch zweifellos hilfreich, die auf der EL-API basierende Anpassungstechnologie zu verstehen.