Explication détaillée de la mise en cache de contenu dynamique sous JSP 2.0
Auteur:Eve Cole
Date de mise à jour:2009-07-03 16:56:34
La mise en cache de contenu est l'une des techniques d'optimisation les plus courantes dans les applications Web et peut être facilement mise en œuvre. Par exemple, vous pouvez utiliser une balise JSP personnalisée - appelons-la <jc:cache> - avec <jc:cache> et </jc:cache> pour encapsuler chaque fragment de page qui doit être mis en cache. Toute balise personnalisée peut contrôler le moment où la partie qu'elle contient (c'est-à-dire le fragment de page préemballé) est exécutée et les résultats de sortie dynamiques peuvent être capturés. La balise <jc:cache> permet à un conteneur JSP (tel que Tomcat) de générer du contenu une seule fois, en tant que variables JSP à l'échelle de l'application, pour stocker chaque fragment de cache. Chaque fois que la page JSP est exécutée, la balise personnalisée chargera le fragment de page mis en cache sans avoir à réexécuter le code JSP pour générer la sortie. Dans le cadre du projet Jakarta, la bibliothèque de tags a été développée grâce à cette technologie. Cela fonctionne bien lorsque le contenu mis en cache n'a pas besoin d'être personnalisé pour chaque utilisateur ou demande.
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'un petit nombre 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="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>
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 mis en cache peut être entouré de balises d'ouverture (<jc:cache>) et de balises de fermeture (</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="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>
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 de l'API du langage d'expression JSP 2.0
Les conteneurs JSP (tels que Tomcat) évaluent les expressions dans les pages JSP à l'aide de l'API EL et peuvent être utilisés 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)
lance JspException {
essayer {
si (expr.indexOf("${") == -1)
retour expr;
Évaluateur ExpressionEvaluator
= jspContext.getExpressionEvaluator();
return évaluateur.evaluate(expr, type,
jspContext.getVariableResolver(), null);
} catch (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, l'expression JSP qui doit être évalué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" 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">
<version-tlib>1.0</version-tlib>
<short-name>jc</short-name>
<uri>http://devsphere.com/articles/jspcache</uri>
<balise>
<nom>cache</nom>
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
<body-content>sans script</body-content>
<attribut>
<nom>identifiant</nom>
<obligatoire>vrai</obligatoire>
<rtexprvalue>vrai</rtexprvalue>
</attribut>
<attribut>
<nom>portée</nom>
<obligatoire>faux</obligatoire>
<rtexprvalue>faux</rtexprvalue>
</attribut>
</tag>
<balise>
<nom>dynamique</nom>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>vide</body-content>
<attribut>
<nom>expression</nom>
<obligatoire>vrai</obligatoire>
<rtexprvalue>faux</rtexprvalue>
</attribut>
</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" 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">
<paramètre-contexte>
<param-name>com.devsphere.articles.jspcache.enabled</param-name>
<param-value>vrai</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>
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(), qui est la classe CacheTag héritée 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";
identifiant de chaîne privé ;
portée internationale privée ;
cache booléen privéEnabled ; public CacheTag() {
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 ;
autre
lancer 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();
Cache de chaîneEnabledParam
= application.getInitParameter(CACHE_ENABLED);
cacheEnabled = cacheEnabledParam != null
&& cacheEnabledParam.equals("true");
si (cacheEnabled) {
Chaîne mise en cacheOutput
= (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);
} autre
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 classe publique CacheTag étend SimpleTagSupport {
... public booléen isCacheEnabled() {
retourner 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(String expr) {
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 = (Chaîne) 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 la 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ésumer
Pour développer de grandes applications au niveau de l'entreprise, vous devez envisager d'utiliser un framework prenant en charge de meilleurs mécanismes de mise en cache au lieu d'utiliser uniquement des variables JSP. Mais il est sans aucun doute utile de comprendre la technologie de personnalisation basée sur l'API EL.