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 Bearbeitung von JSP-Seiten erfordern Änderungen an der Geschäftslogik der Anwendung normalerweise mehr Arbeit und Tests. Wenn eine Seite Informationen aus mehreren zusammengesetzten Quellen zusammenfasst, sind außerdem nur geringfügige Änderungen an der Webebene 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, kann der Cache-Inhalt durch Start-Tags (<jc:cache>) und End-Tags (</jc:cache>) gekapselt 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 des JSP 2.0 Expression Language API-
JSP-Containers (z. B. Tomcat) zum Anwenden der EL-API-Ausdrücke in der JSP Seite werden Werte zugewiesen 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;öffentliche Klasse JspUtils { public static Object eval(String expr, Class type, JspContext jspContext) throwsJspException { versuchen { 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); } } ... } |
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, das heißt, der JSP-Ausdruck muss jedes Mal zugewiesen werden, 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"kodierung="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> <Kurzname>jc</Kurzname> <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> |
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"kodierung="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> |
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, die die CacheTag-Klasse von SimpleTagSupport erbt. 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 { öffentlicher statischer endgültiger String 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-Bereich) { 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; Andernfalls wird eine neue IllegalArgumentException ausgelöst ( "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.
... Die öffentliche Klasse CacheTag erweitert SimpleTagSupport { ... public void doTag() löst JspException, IOException aus { JspContext jspContext = getJspContext(); ServletContext-Anwendung = ((PageContext) jspContext).getServletContext(); String cacheEnabledParam= application.getInitParameter(CACHE_ENABLED); CacheEnabled = CacheEnabledParam! = Null && cacheEnabledParam.equals("true"); if(cacheEnabled) { StringcachedOutput= (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(cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } sonst 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.
Die Methode IsCacheEnabled() gibt den Wert von cacheEnabled zurück, der von doTag() initialisiert wurde. ...die ö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-Ausdruck; public void setExpr(String-Ausdruck) { this.expr = expr; } public void doTag() löst JspException, IOException aus { String-Ausgabe = "${" + expr + "}"; CacheTag ancestor = (CacheTag) findAncestorWithClass (dies, 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 einem 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.
Zusammenfassung
Inhaltscaching ist eine sehr einfach zu verwendende Methode zur Verbesserung der Leistung von Webanwendungen. Dieser Artikel konzentriert sich auf die Verwendung der JSP-Ausdruckssprache zum Anpassen des Cache-Inhalts für jeden Benutzer oder jede Anforderung. Die in diesem Artikel kurz vorgestellten Tag-Bibliotheken eignen sich für kleine Web-Apps und können die Leistung mittelgroßer Anwendungen verbessern. Für die Entwicklung umfangreicher Anwendungen auf Unternehmensebene sollten Sie die Verwendung eines Frameworks in Betracht ziehen, das bessere Caching-Mechanismen unterstützt, anstatt nur JSP-Variablen zu verwenden.