Cet article améliore la technique décrite ci-dessus en utilisant le langage d'expression (EL) JSP 2.0, permettant aux pages JSP de personnaliser le contenu mis en cache pour chaque requête et utilisateur. Les fragments de page de cache peuvent contenir des expressions JSP qui ne sont pas évaluées par le conteneur JSP. Les valeurs de ces expressions sont déterminées par des balises personnalisées à chaque exécution de la page. Par conséquent, la création de contenu dynamique est optimisée, mais les fragments mis en cache peuvent contenir des parties du contenu généré par chaque requête à l'aide du langage d'expression natif JSP. Avec l'aide de l'API EL JSP 2.0, les développeurs Java peuvent rendre cela possible en utilisant des langages d'expression.
Mise en cache du contenu VS mise en cache des données
La mise en cache du contenu n'est pas la seule option. Par exemple, les données extraites d'une base de données peuvent également être mises en cache. En fait, la mise en cache des données peut être plus efficace puisque les informations stockées ne contiennent pas de balisage HTML et nécessitent moins de mémoire. Toutefois, dans de nombreux cas, la mise en cache en mémoire est plus facile à mettre en œuvre. Supposons que dans un certain cas, une application se compose d'un grand nombre d'objets de transaction, occupe d'importantes ressources CPU, génère des données complexes et utilise des pages JSP pour présenter les données. Tout fonctionnait bien jusqu'au jour où la charge sur le serveur a soudainement augmenté et qu'une solution d'urgence s'est avérée nécessaire. À l’heure actuelle, établir une couche de cache entre l’objet de transaction et la couche de présentation est une solution très bonne et efficace. Mais les pages JSP qui mettent en cache du contenu dynamique doivent être modifiées très rapidement et en douceur. Par rapport à la simple édition de pages JSP, les modifications apportées à la logique métier de l'application nécessitent généralement plus de travail et de tests. De plus, si une page regroupe des informations provenant de plusieurs sources composites, la couche Web n'a besoin que d'une petite quantité de modifications. Le problème est que l'espace du cache doit être libéré lorsque les informations mises en cache deviennent obsolètes, et l'objet de transaction doit savoir quand cela se produit. Cependant, lors du choix de mettre en œuvre la mise en cache du contenu ou des données, ou d'autres techniques d'optimisation, de nombreux facteurs doivent être pris en compte, et il existe parfois des exigences particulières pour le programme en cours de développement. La mise en cache des données et la mise en cache du contenu ne s’excluent pas nécessairement mutuellement, elles peuvent être utilisées ensemble. Par exemple, dans une application basée sur une base de données, les données extraites de la base de données et le code HTML qui présente les données sont mis en cache séparément. C'est un peu similaire à l'utilisation de JSP pour générer des modèles en temps réel. Les techniques d'API basées sur EL abordées dans cet article illustrent comment utiliser JSP EL pour charger des données dans des modèles de rendu.
Mise en cache du contenu dynamique à l'aide de variables JSP
Chaque fois que vous implémentez un mécanisme de mise en cache, vous avez besoin d'une méthode pour stocker les objets en cache. Dans cet article, les objets de type String sont impliqués. Une option consiste à utiliser un cadre de mise en cache d'objets ou à utiliser des cartes Java pour implémenter un schéma de mise en cache personnalisé. JSP dispose déjà de ce que l'on appelle des « attributs de portée » ou des « variables JSP » pour fournir un mappage ID-objet, ce que requiert le mécanisme de mise en cache. Pour utiliser la portée d'une page ou d'une requête, cela n'a pas de sens, alors que dans la portée de l'application, c'est un bon endroit pour stocker le contenu en cache car il est partagé par tous les utilisateurs et toutes les pages. La portée de la session peut également être utilisée lorsqu'une mise en cache distincte est requise pour chaque utilisateur, mais cela n'est pas très efficace. La bibliothèque de balises JSTL peut être utilisée avec celle-ci pour mettre en cache le contenu, en utilisant des variables JSP comme indiqué dans l'exemple suivant :
<%@ 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> |
Le fragment de page mis en cache génère les résultats à l'aide de l'instruction suivante :
${applicationScope.cachedFragment} |
Que se passe-t-il lorsque le fragment de cache doit être personnalisé pour chaque requête ?
Par exemple, si vous souhaitez inclure un compteur, vous devez mettre en cache deux fragments :
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${sessionScope.counter == null}"> <c:set var="compteur" scope="session" value="0"/> </c:if><c:set var="compteur" value="${compteur+1}" scope="session"/> <c:if test="${empty cachedFragment1}"> <c:set var="cachedFragment1" scope="application"> ... </c:set></c:if><c:if test="${vide cachedFragment2}"> <c:set var="cachedFragment2" scope="application"> ... </c:set></c:if> |
Vous pouvez utiliser l'instruction suivante pour afficher le contenu du cache :
${cachedFragment1} ${counter} ${cachedFragment2} |
Avec l'aide d'une bibliothèque de balises dédiée, la mise en cache des fragments de page nécessitant une personnalisation devient extrêmement simple. Comme mentionné ci-dessus, le contenu du cache peut être encapsulé par des balises de début (<jc:cache>) et des balises de fin (</jc:cache>). Chaque personnalisation peut être exprimée à l'aide d'une autre balise (<jc:dynamic expr="..."/>) pour générer une expression JSP (${...}). Le contenu dynamique est mis en cache à l'aide d'expressions JSP et attribué à chaque fois que le contenu mis en cache est émis. Vous pouvez voir comment cela est réalisé dans la section ci-dessous. Counter.jsp met en cache un fragment de page contenant un compteur. Le compteur augmentera automatiquement de 1 à chaque fois que l'utilisateur actualisera la page.
<%@ 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="compteur" scope="session" value="0"/> </c:if><c:set var="compteur" value="${compteur+1}" scope="session"/> <jc:cache id="cachedFragmentWithCounter"> ... <jc:dynamic expr="sessionScope.counter"/> ... </jc:cache> |
Les variables JSP sont faciles à utiliser et constituent une bonne solution de mise en cache de contenu pour les applications Web simples. Cependant, si l’application génère beaucoup de contenu dynamique, ne pas avoir de contrôle sur la taille du cache est définitivement un problème. Un framework de cache dédié peut fournir une solution plus puissante, permettant la surveillance du cache, la limitation de la taille du cache, le contrôle de la politique de cache, etc...
Utilisation du conteneur JSP de l'API du langage d'expression JSP 2.0
(tel que Tomcat) pour appliquer les expressions de l'API EL dans la JSP La page se voit attribuer des valeurs et peut être utilisée par le code Java. Cela permet le développement en dehors des pages Web à l'aide de JSP EL, par exemple sur des fichiers XML, des ressources textuelles et des scripts personnalisés. L'API EL est également utile lorsque vous devez contrôler le moment où les expressions d'une page Web sont affectées ou écrites. Par exemple, les fragments de page mis en cache peuvent contenir des expressions JSP personnalisées, et l'API EL sera utilisée pour attribuer ou réévaluer ces expressions à chaque fois que le contenu mis en cache est émis.
L'article fournit un exemple de programme (voir la section ressources en fin d'article). Cette application contient une classe Java (JspUtils) et une méthode eval() dans la classe. Cette méthode a trois paramètres : L'expression JSP, le type attendu. de l'expression et un objet de contexte JSP. La méthode Eval() récupère l'ExpressionEvaluator du contexte JSP et appelle la méthode évaluer(), en transmettant l'expression, le type attendu de l'expression et une variable obtenue à partir du contexte JSP. La méthode JspUtils.eval() renvoie la valeur de l'expression.
paquet com.devsphere.articles.jspcache ; importer javax.servlet.jsp.JspContext ; importer javax.servlet.jsp.JspException ; importer javax.servlet.jsp.PageContext ; importer javax.servlet.jsp.el.ELException ; importer javax.servlet.jsp.el.ExpressionEvaluator ; importer java.io.IOException; classe publique JspUtils { évaluation d'objet statique publique (expr de chaîne, type de classe, JspContext jspContext) throwsJspException { essayer { if (expr.indexOf("${") == -1) return expr; ExpressionEvaluator évaluateur= jspContext.getExpressionEvaluator(); return évaluateur.evaluate(expr, type, jspContext.getVariableResolver(), null); } attraper (ELException e) { lancer une nouvelle JspException(e); } } ... } |
Remarque : JspUtils.eval() encapsule principalement le ExpressionEvaluator standard. Si expr ne contient pas ${, l'API JSP EL n'est pas appelée car il n'y a pas d'expression JSP.
Création d'un fichier de descripteur de bibliothèque de balises (TLD) Une
bibliothèque de balises JSP nécessite un fichier de descripteur de bibliothèque de balises (TLD) pour personnaliser le nom des balises, leurs attributs et les classes Java qui fonctionnent sur la balise. jspcache.tld décrit deux balises personnalisées. <jc:cache> a deux attributs : l'identifiant du fragment de page mis en cache et la portée JSP – la portée du contenu de la page JSP qui doit toujours être stockée. <jc:dynamic> n'a qu'un seul attribut, c'est-à-dire que l'expression JSP doit être attribuée à chaque fois que le fragment de cache est généré. Le fichier TLD mappe ces deux balises personnalisées aux classes CacheTag et DynamicTag comme suit :
<?xml version="1.0" encodage="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-jsptalibrary_2_0.xsd" version="2.0"> <version-tlib>1.0</version-tlib> <nom-abrégé>jc</nom-abrégé> <uri>http://devsphere.com/articles/jspcache</uri> <balise> <nom>cache</nom> <tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class> <contenu corporel>sans script</contenu corporel> <attribut> <nom>id</nom> <obligatoire>vrai</obligatoire> <rtexprvalue>true</rtexprvalue> </attribute> <attribut> <nom>portée</nom> <obligatoire>false</obligatoire> <rtexprvalue>false</rtexprvalue> </attribute> </tag> <tag> <nom>dynamique</nom> <tag-class> com.devsphere.articles.jspcache.DynamicTag</tag-class> <contenu du corps>vide</contenu du corps> <attribut> <nom>expr</nom> <obligatoire>vrai</obligatoire> <rtexprvalue>false</rtexprvalue> </attribute> </tag></taglib> |
Le fichier TLD est inclus dans le fichier descripteur de l'application Web (web.xml). Ces cinq fichiers contiennent également un paramètre initial indiquant si le cache est disponible.
<?xml version="1.0" encodage="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"> <param-contexte> <nom-param> com.devsphere.articles.jspcache.enabled</param-name> <valeur-param>true</valeur-param> </param-contexte> <jsp-config> <taglib> <taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri> <emplacement-taglib>/WEB-INF/jspcache.tld</emplacement-taglib> </taglib> </jsp-config></web-app> |
Comprendre le mécanisme de fonctionnement de <jc:cache>
Le conteneur JSP crée une instance CacheTag pour chaque balise <jc:cache> dans la page JSP afin de la traiter. Le conteneur JSP est chargé d'appeler les méthodes setJsp(), setParent() et setJspBody(), dont la classe CacheTag hérite de SimpleTagSupport. Le conteneur JSP appelle également la méthode setter pour chaque attribut de la balise manipulée. Les méthodes SetId() et setScope() stockent la valeur de l'attribut dans un champ privé. Cette valeur a été initialisée avec la valeur par défaut à l'aide du constructeur CacheTag().
paquet com.devsphere.articles.jspcache ; importer javax.servlet.ServletContext ; importer javax.servlet.jsp.JspContext ; importer javax.servlet.jsp.JspException ; importer javax.servlet.jsp.PageContext ; importer javax.servlet.jsp.tagext.SimpleTagSupport ; importer java.io.IOException; importer java.io.StringWriter; la classe publique CacheTag étend SimpleTagSupport { chaîne finale statique publique CACHE_ENABLED = "com.devsphere.articles.jspcache.enabled" ; ID de chaîne privé ; portée int privée ; cache booléen privéEnabled ; cacheTag public() { identifiant = nul ; portée = PageContext.APPLICATION_SCOPE ; }public void setId (ID de chaîne) { this.id = identifiant; } public void setScope (portée de la chaîne) { this.scope = JspUtils.checkScope(scope); } ... } La méthode setScope() appelle JspUtils.checkScope() pour vérifier la valeur de propriété de la portée qui a été convertie du type String en type int. ... classe publique JspUtils { ... public static int checkScope (portée de la chaîne) { si ("page".equalsIgnoreCase(scope)) renvoyer PageContext.PAGE_SCOPE ; sinon si ("requête".equalsIgnoreCase(scope)) renvoyer PageContext.REQUEST_SCOPE ; sinon si ("session".equalsIgnoreCase(scope)) renvoyer PageContext.SESSION_SCOPE ; sinon si ("application".equalsIgnoreCase(scope)) renvoyer PageContext.APPLICATION_SCOPE ; sinon, lancez une nouvelle IllegalArgumentException ( "Portée invalide : " + portée }}) |
Une fois que l'instance CacheTag est prête à fonctionner sur la balise, le conteneur JSP appelle la méthode doTag() et obtient le contexte JSP à l'aide de getJspContext(). Cet objet est converti en PageContext, afin que la méthode getServletContext() puisse être appelée. Le contexte du servlet est utilisé pour obtenir la valeur du paramètre d'initialisation, qui indique si le mécanisme de mise en cache est activé. Si la mise en cache est activée, doTag() tente d'obtenir le fragment de page mis en cache en utilisant les valeurs des attributs id et scope. Si le fragment de page n'a pas été mis en cache, doTag() utilise getJspBody().invoke() pour exécuter le code JSP encapsulé par <jc:cache> et </jc:cache>. La sortie générée par le corps JSP est mise en mémoire tampon dans un StringWriter et récupérée par la méthode toStirng(). De cette façon, doTag() appelle la méthode setAttribute() du contexte JSP pour créer une nouvelle variable JSP. Cette variable contrôle le contenu du cache pouvant contenir des expressions JSP (${…}). Ces expressions sont attribuées par JspUtils.eval() avant de générer le contenu à l'aide de jspContext.getOut().print(). Ce comportement se produit uniquement lorsque la mise en cache est activée. Sinon, doTag() exécute simplement le corps JSP via getJspBody().invoke(null) et le résultat de sortie n'est pas mis en cache.
... la classe publique CacheTag étend SimpleTagSupport { ... public void doTag() lance JspException, IOException { JspContext jspContext = getJspContext(); Application ServletContext = ((PageContext) jspContext).getServletContext(); String cacheEnabledParam= application.getInitParameter(CACHE_ENABLED); cacheEnabled = cacheEnabledParam != null && cacheEnabledParam.equals("true"); si (cacheEnabled) { String cachedOutput= (String) jspContext.getAttribute(id, scope); si (cachedOutput == null) { Tampon StringWriter = new StringWriter(); getJspBody().invoke(buffer); cachedOutput = buffer.toString(); jspContext.setAttribute(id, cachedOutput, scope); } Chaîne évaluéeOutput = (Chaîne) JspUtils.eval( cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } sinon getJspBody().invoke(null); } ... } |
Notez qu'un seul appel JspUtils.eval() évalue toutes les expressions ${…}. Car un texte contenant un grand nombre de structures ${...} est aussi une expression. Chaque fragment de cache peut être traité comme une expression JSP complexe. La méthode IsCacheEnabled() renvoie la valeur de cacheEnabled, qui a été initialisée par doTag().
La méthode IsCacheEnabled() renvoie la valeur de cacheEnabled, qui a été initialisée par doTag(). ...la classe publique CacheTag étend SimpleTagSupport { ... public boolean isCacheEnabled() { return cacheEnabled ; } } |
La balise <jc:cache> permet aux développeurs de pages de choisir l'ID des fragments de page mis en cache. Cela permet aux fragments de page mis en cache d'être partagés par plusieurs pages JSP, ce qui est utile lors de la réutilisation du code JSP. Mais un protocole de dénomination est encore nécessaire pour éviter d’éventuels conflits. Cet effet secondaire peut être évité en modifiant la classe CacheTag pour inclure l'URL dans l'ID automatique.
Comprendre ce que fait <jc:dynamic>
Chaque <jc:dynamic> est géré par une instance de la classe DynamicTag et la méthode setExpr() stocke la valeur de l'attribut expr dans un champ privé. La méthode DoTag() crée une expression JSP et ajoute le préfixe ${ et le suffixe } à la valeur de l'attribut expr. Ensuite, doTag() utilise findAncestorWithClass() pour rechercher le gestionnaire CacheTag de <jc:cache> qui contient l'élément de balise <jc:dynamic>. Si elle n'est pas trouvée ou si la mise en cache est désactivée, l'expression JSP est évaluée avec JspUtils.eval() et la valeur est affichée. Sinon, doTag() génère une expression sans valeur.
paquet com.devsphere.articles.jspcache ; importer javax.servlet.jsp.JspException ; importer javax.servlet.jsp.tagext.SimpleTagSupport ; importer java.io.IOException ; la classe publique DynamicTag étend SimpleTagSupport { expression de chaîne privée ; public void setExpr (expression de chaîne) { this.expr = expr; } public void doTag() lance JspException, IOException { Sortie de chaîne = "${" + expr + "}" ; Ancêtre CacheTag = (CacheTag) findAncestorWithClass (ceci, CacheTag.class); if (ancêtre == null || !ancestor.isCacheEnabled()) sortie = (String)JspUtils.eval (sortie, String.class, getJspContext()); getJspContext().getOut().print(output); } } |
En analysant le code ci-dessus, vous remarquerez que <jc:cache> et <jc:dynamic> coopèrent pour parvenir à une solution aussi efficace que possible. Si le cache est disponible, le fragment de page est placé dans la mémoire tampon avec l'expression JSP générée par <jc:dynamic> et une valeur CacheTag lui est attribuée. Si la mise en cache est désactivée, la mise en cache n'a plus de sens et <jc:cache> exécute simplement sa partie du corps JSP, laissant DynamicTag attribuer des valeurs à l'expression JSP. La désactivation de la mise en cache est parfois nécessaire, notamment pendant le processus de développement lorsque le contenu change et que les pages JSP sont recompilées. Bien entendu, la mise en cache doit être activée dans l’environnement de production après le développement.
Résumé
La mise en cache du contenu est une méthode très simple à utiliser pour améliorer les performances des applications Web. Cet article se concentre sur l'utilisation du langage d'expression JSP pour personnaliser le contenu du cache pour chaque utilisateur ou requête. Les bibliothèques de balises brièvement présentées tout au long de cet article conviennent aux petites applications Web et peuvent améliorer les performances des applications de taille moyenne. Pour développer des applications d'entreprise à grande échelle, vous devez envisager d'utiliser une infrastructure prenant en charge de meilleurs mécanismes de mise en cache, plutôt que d'utiliser uniquement des variables JSP.