Explicación detallada del almacenamiento en caché de contenido dinámico en JSP 2.0
Autor:Eve Cole
Fecha de actualización:2009-07-03 16:56:34
El almacenamiento en caché de contenido es una de las técnicas de optimización más comunes en aplicaciones web y se puede implementar fácilmente. Por ejemplo, puede utilizar una etiqueta JSP personalizada (llamémosla <jc:cache>) con <jc:cache> y </jc:cache> para encapsular cada fragmento de página que debe almacenarse en caché. Cualquier etiqueta personalizada puede controlar cuándo se ejecuta la parte que contiene (es decir, el fragmento de página empaquetado previamente) y se pueden capturar los resultados de salida dinámica. La etiqueta <jc:cache> permite que un contenedor JSP (como Tomcat) genere contenido solo una vez, como variables JSP de toda la aplicación, para almacenar cada fragmento de caché. Cada vez que se ejecuta la página JSP, la etiqueta personalizada cargará el fragmento de página almacenado en caché sin tener que ejecutar el código JSP nuevamente para generar la salida. Como parte del proyecto de Yakarta, la biblioteca de etiquetas se desarrolló utilizando esta tecnología. Funciona bien cuando no es necesario personalizar el contenido almacenado en caché para cada usuario o solicitud.
Este artículo mejora la técnica descrita anteriormente mediante el uso del lenguaje de expresión (EL) JSP 2.0, lo que permite que las páginas JSP personalicen el contenido almacenado en caché para cada solicitud y usuario. Los fragmentos de página de caché pueden contener expresiones JSP que no son evaluadas por el contenedor JSP. Los valores de estas expresiones están determinados por etiquetas personalizadas cada vez que se ejecuta la página. Por lo tanto, la creación de contenido dinámico está optimizada, pero los fragmentos almacenados en caché pueden contener partes del contenido generado por cada solicitud utilizando el lenguaje de expresión JSP nativo. Con la ayuda de JSP 2.0 EL API, los desarrolladores de Java pueden hacer esto posible utilizando lenguajes de expresión.
Almacenamiento en caché de contenido versus almacenamiento en caché de datos El almacenamiento en caché de contenido no es la única opción. Por ejemplo, los datos extraídos de una base de datos también se pueden almacenar en caché. De hecho, el almacenamiento en caché de datos puede ser más eficiente ya que la información almacenada no contiene marcado HTML y requiere menos memoria. Sin embargo, en muchos casos, el almacenamiento en caché en memoria es más fácil de implementar. Supongamos que, en un caso determinado, una aplicación consta de una gran cantidad de objetos de transacción, ocupa importantes recursos de CPU, genera datos complejos y utiliza páginas JSP para presentar los datos. Todo funcionaba bien hasta que un día la carga en el servidor aumentó repentinamente y se necesitaba una solución de emergencia. En este momento, establecer una capa de caché entre el objeto de transacción y la capa de presentación es una solución muy buena y efectiva. Pero las páginas JSP que almacenan contenido dinámico en caché deben modificarse de manera muy rápida y fluida. En comparación con la edición simple de páginas JSP, los cambios en la lógica empresarial de la aplicación generalmente requieren más trabajo y pruebas; además, si una página agrega información de múltiples fuentes compuestas, la capa web solo necesita una pequeña cantidad de cambios; El problema es que es necesario liberar espacio en caché cuando la información almacenada en caché se vuelve obsoleta, y el objeto de transacción debe saber cuándo sucede esto. Sin embargo, al elegir implementar el almacenamiento en caché de contenido o de datos, u otras técnicas de optimización, hay muchos factores que deben considerarse y, a veces, existen requisitos especiales para el programa que se está desarrollando.
El almacenamiento en caché de datos y el almacenamiento en caché de contenido no son necesariamente excluyentes entre sí, se pueden utilizar juntos. Por ejemplo, en una aplicación basada en una base de datos; los datos extraídos de la base de datos y el HTML que los presenta se almacenan en caché por separado. Esto es algo similar a usar JSP para generar plantillas en tiempo real. Las técnicas de API basadas en EL que se analizan en este artículo ilustran cómo utilizar JSP EL para cargar datos en plantillas de representación.
Almacenamiento en caché de contenido dinámico utilizando variables JSP Siempre que implemente un mecanismo de almacenamiento en caché, necesita un método para almacenar objetos en caché. En este artículo, están involucrados objetos de tipo String. Una opción es utilizar un marco de almacenamiento en caché de objetos o utilizar mapas de Java para implementar un esquema de almacenamiento en caché personalizado. JSP ya tiene lo que se denomina "atributos de alcance" o "variables JSP" para proporcionar mapeo ID-objeto, que es lo que requiere el mecanismo de almacenamiento en caché. Para usar el alcance de la página o de la solicitud, esto no tiene sentido, mientras que en el alcance de la aplicación, este es un buen lugar para almacenar contenido en caché, ya que es compartido por todos los usuarios y páginas. El alcance de la sesión también se puede utilizar cuando se requiere un almacenamiento en caché independiente para cada usuario, pero esto no es muy eficiente. La biblioteca de etiquetas JSTL se puede utilizar para almacenar en caché el contenido mediante el uso de variables JSP, como se muestra en el siguiente ejemplo:
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><c:if test="${empty cachedFragment}">
<c:set var="cachedFragment" alcance="aplicación">
...
</c:set></c:if>
El fragmento de página almacenado en caché genera los resultados utilizando la siguiente declaración:
${applicationScope.cachedFragment}
¿Qué sucede cuando es necesario personalizar el fragmento de caché para cada solicitud? Por ejemplo, si desea incluir un contador, debe almacenar en caché dos fragmentos:
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><c:if test="${sessionScope.counter == null}"> <c :set var="contador" alcance="sesión" valor="0"/></c:if><c:set var="contador" valor="${contador+1}" alcance="sesión"/ ><c:if prueba="${empty cachedFragment1}">
<c:set var="cachedFragment1" alcance="aplicación">
...
</c:set></c:if><c:if test="${empty cachedFragment2}">
<c:set var="cachedFragment2" alcance="aplicación">
...
</c:set></c:if>
Puede utilizar la siguiente declaración para generar el contenido de la caché:
${cachedFragment1} ${counter} ${cachedFragment2}
Con la ayuda de una biblioteca de etiquetas dedicada, el almacenamiento en caché de fragmentos de páginas que requieren personalización se vuelve extremadamente fácil. Como se mencionó anteriormente, el contenido almacenado en caché se puede encerrar mediante etiquetas de apertura (<jc:cache>) y etiquetas de cierre (</jc:cache>). Cada personalización se puede expresar usando otra etiqueta (<jc:dynamic expr="..."/>) para generar una expresión JSP (${...}). El contenido dinámico se almacena en caché mediante expresiones JSP y se asigna cada vez que se emite el contenido almacenado en caché. Puede ver cómo se logra esto en la sección siguiente. Counter.jsp almacena en caché un fragmento de página que contiene un contador. El contador aumentará automáticamente en 1 cada vez que el usuario actualice la página.
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><%@ taglib prefix="jc" uri=" http://devsphere.com/ artículos/jspcache " %><c:if test="${sessionScope.counter == null}">
<c:set var="contador" alcance="sesión" valor="0"/></c:if><c:set var="contador" valor="${counter+1}" alcance="sesión "/><jc:cache id="cachedFragmentWithCounter">
... <jc:expr dinámico="sessionScope.counter"/>
...</jc:caché>
Las variables JSP son fáciles de usar y son una buena solución de almacenamiento en caché de contenido para aplicaciones web simples. Sin embargo, si la aplicación genera una gran cantidad de contenido dinámico, no tener control sobre el tamaño de la caché es definitivamente un problema. Un marco de caché dedicado puede proporcionar una solución más poderosa, permitiendo monitorear el caché, limitar el tamaño del caché, controlar la política del caché, etc.
Uso de la API del lenguaje de expresión JSP 2.0
Los contenedores JSP (como Tomcat) evalúan expresiones en páginas JSP utilizando la API EL y pueden ser utilizados por código Java. Esto permite el desarrollo fuera de páginas web utilizando JSP EL, por ejemplo, en archivos XML, recursos basados en texto y scripts personalizados. La API EL también es útil cuando necesita controlar cuándo se asignan expresiones en una página web o cuándo se escriben expresiones relacionadas con ellas. Por ejemplo, los fragmentos de página almacenados en caché pueden contener expresiones JSP personalizadas y la API EL se utilizará para asignar o reevaluar estas expresiones cada vez que se emita el contenido almacenado en caché.
El artículo proporciona un programa de ejemplo (consulte la sección de recursos al final del artículo). Esta aplicación contiene una clase Java (JspUtils) y un método eval() en la clase. Este método tiene tres parámetros: expresión JSP, el tipo esperado. de la expresión y un objeto de contexto JSP. El método Eval() obtiene ExpressionEvaluator del contexto JSP y llama al método evalua(), pasando la expresión, el tipo esperado de la expresión y una variable obtenida del contexto JSP. El método JspUtils.eval() devuelve el valor de la expresión.
paquete com.devsphere.articles.jspcache;
importar javax.servlet.jsp.JspContext;
importar javax.servlet.jsp.JspException;
importar javax.servlet.jsp.PageContext;
importar javax.servlet.jsp.el.ELException;
importar javax.servlet.jsp.el.ExpressionEvaluator;
importar java.io.IOException; clase pública JspUtils {
evaluación de objeto estático público (
Expr de cadena, tipo de clase, JspContext jspContext)
lanza JspException {
intentar {
si (expr.indexOf("${") == -1)
volver expr;
Evaluador ExpressionEvaluator
= jspContext.getExpressionEvaluator();
devolver evaluador.evaluate(expr, tipo,
jspContext.getVariableResolver(), nulo);
} captura (ELException e) {
lanzar nueva JspException(e);
}
}
...}
Nota: JspUtils.eval() encapsula principalmente el ExpressionEvaluator estándar. Si expr no contiene ${, no se llama a JSP EL API porque no hay ninguna expresión JSP.
Creación de un archivo descriptor de biblioteca de etiquetas (TLD) Una biblioteca de etiquetas JSP requiere un archivo descriptor de biblioteca de etiquetas (TLD) para personalizar la denominación de las etiquetas, sus atributos y las clases Java que operan en la etiqueta. jspcache.tld describe dos etiquetas personalizadas. <jc:cache> tiene dos atributos: la identificación del fragmento de página almacenado en caché y el alcance JSP: el alcance del contenido de la página JSP que siempre debe almacenarse. <jc:dynamic> tiene solo un atributo, la expresión JSP que debe evaluarse cada vez que se genera el fragmento de caché. El archivo TLD asigna estas dos etiquetas personalizadas a las clases CacheTag y DynamicTag de la siguiente manera:
<?xml versión="1.0" codificación="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"
versión="2.0">
<tlib-versión>1.0</tlib-versión>
<nombre-corto>jc</nombre-corto>
<uri>http://devsphere.com/articles/jspcache</uri>
<etiqueta>
<nombre>caché</nombre>
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
<body-content>sin secuencias de comandos</body-content>
<atributo>
<nombre>id</nombre>
<requerido>verdadero</requerido>
<rtexprvalue>verdadero</rtexprvalue>
</atributo>
<atributo>
<nombre>alcance</nombre>
<requerido>falso</requerido>
<rtexprvalue>falso</rtexprvalue>
</atributo>
</etiqueta>
<etiqueta>
<nombre>dinámico</nombre>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>vacío</body-content>
<atributo>
<nombre>expr</nombre>
<requerido>verdadero</requerido>
<rtexprvalue>falso</rtexprvalue>
</atributo>
</tag></taglib>
El archivo TLD se incluye en el archivo descriptor de la aplicación web (web.xml). Estos cinco archivos también contienen un parámetro inicial que indica si el caché está disponible.
<?xml versión="1.0" codificación="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"
versión="2.4">
<parámetro-contexto>
<param-name>com.devsphere.articles.jspcache.enabled</param-name>
<valor-param>verdadero</valor-param>
</context-param>
<jsp-config>
<etiqueta>
<taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri>
<ubicación-taglib>/WEB-INF/jspcache.tld</ubicación-taglib>
</taglib>
</jsp-config></web-app>
Comprenda el mecanismo de funcionamiento de <jc:cache>. El contenedor JSP crea una instancia de CacheTag para cada etiqueta <jc:cache> en la página JSP para procesarla. El contenedor JSP es responsable de llamar a los métodos setJsp(), setParent() y setJspBody(), que es la clase CacheTag heredada de SimpleTagSupport. El contenedor JSP también llama al método de establecimiento para cada atributo de la etiqueta manipulada. Los métodos SetId() y setScope() almacenan el valor del atributo en un campo privado. Este valor se ha inicializado con el valor predeterminado utilizando el constructor CacheTag().
paquete com.devsphere.articles.jspcache;
importar javax.servlet.ServletContext;
importar javax.servlet.jsp.JspContext;
importar javax.servlet.jsp.JspException;
importar javax.servlet.jsp.PageContext;
importar javax.servlet.jsp.tagext.SimpleTagSupport;
importar java.io.IOException; importar java.io.StringWriter;
La clase pública CacheTag extiende SimpleTagSupport {
Cadena final estática pública CACHE_ENABLED
= "com.devsphere.articles.jspcache.enabled";
identificación de cadena privada;
alcance interno privado;
caché booleano privadoEnabled; CacheTag público() {
id = nulo; alcance = PageContext.APPLICATION_SCOPE;
} setId público vacío (ID de cadena) {
this.id = identificación;
} setScope público vacío (alcance de cadena) {
this.scope = JspUtils.checkScope(alcance);
}
...}
El método setScope() llama a JspUtils.checkScope() para verificar el valor de propiedad del alcance que se ha convertido de String a tipo int.
...clase pública JspUtils {
...
público estático int checkScope (alcance de cadena) {
if ("página".equalsIgnoreCase(alcance))
devolver PageContext.PAGE_SCOPE;
else if ("solicitud".equalsIgnoreCase(alcance))
devolver PageContext.REQUEST_SCOPE;
else if ("sesión".equalsIgnoreCase(alcance))
devolver PageContext.SESSION_SCOPE;
else if ("aplicación".equalsIgnoreCase(alcance))
devolver PageContext.APPLICATION_SCOPE;
demás
lanzar una nueva IllegalArgumentException(
"Alcance no válido: " + alcance);
}}
Una vez que la instancia de CacheTag está lista para operar con la etiqueta, el contenedor JSP llama al método doTag() y obtiene el contexto JSP usando getJspContext(). Este objeto se convierte como PageContext, de modo que se puede llamar al método getServletContext(). El contexto del servlet se utiliza para obtener el valor del parámetro de inicialización, que indica si el mecanismo de almacenamiento en caché está habilitado. Si el almacenamiento en caché está habilitado, doTag() intenta obtener el fragmento de página almacenado en caché utilizando los valores de los atributos id y alcance. Si el fragmento de página no se ha almacenado en caché, doTag() usa getJspBody().invoke() para ejecutar el código JSP encapsulado por <jc:cache> y </jc:cache>. La salida generada por el cuerpo JSP se almacena en un StringWriter y se recupera mediante el método toStirng(). De esta manera, doTag() llama al método setAttribute() del contexto JSP para crear una nueva variable JSP. Esta variable controla el contenido de la caché que puede contener expresiones JSP (${…}). Estas expresiones son asignadas por JspUtils.eval() antes de generar contenido usando jspContext.getOut().print(). Este comportamiento sólo ocurre cuando el almacenamiento en caché está habilitado. De lo contrario, doTag() simplemente ejecuta el cuerpo JSP a través de getJspBody().invoke(null) y el resultado de salida no se almacena en caché.
...la clase pública CacheTag extiende SimpleTagSupport {
...
public void doTag() lanza JspException, IOException {
JspContext jspContext = getJspContext();
Aplicación ServletContext
= ((PageContext) jspContext).getServletContext();
Caché de cadenaEnabledParam
= aplicación.getInitParameter(CACHE_ENABLED);
cacheEnabled = cacheEnabledParam! = nulo
&& cacheEnabledParam.equals("verdadero");
si (cacheEnabled) {
Salida en caché de cadena
= (Cadena) jspContext.getAttribute(id, alcance);
si (cachedOutput == nulo) {
Búfer StringWriter = nuevo StringWriter();
getJspBody().invoke(búfer);
cachedOutput = buffer.toString();
jspContext.setAttribute(id, cachedOutput, alcance);
} Cadena evaluadaSalida = (Cadena) JspUtils.eval(
salida en caché, String.class, jspContext);
jspContext.getOut().print(salidaevaluada);
} demás
getJspBody().invoke(nulo);
}
...}
Tenga en cuenta que una única llamada a JspUtils.eval() evalúa todas las expresiones ${…}. Porque un texto que contiene una gran cantidad de estructuras ${...} también es una expresión. Cada fragmento de caché se puede procesar como una expresión JSP compleja.
El método IsCacheEnabled() devuelve el valor de cacheEnabled, que ha sido inicializado por doTag().
...la clase pública CacheTag extiende SimpleTagSupport {
... público booleano isCacheEnabled() {
devolver caché habilitado;
}}
La etiqueta <jc:cache> permite a los desarrolladores de páginas elegir el ID de los fragmentos de página almacenados en caché. Esto permite que múltiples páginas JSP compartan fragmentos de páginas en caché, lo cual resulta útil al reutilizar código JSP. Pero todavía se necesita algún protocolo de nombres para evitar posibles conflictos. Este efecto secundario se puede evitar modificando la clase CacheTag para incluir la URL dentro de la ID automática.
Comprender qué está haciendo <jc:dynamic> Cada <jc:dynamic> es manejado por una instancia de la clase DynamicTag, y el método setExpr() almacena el valor del atributo expr en un campo privado. El método DoTag() crea una expresión JSP y agrega ${ prefijo y } sufijo al valor del atributo expr. Luego, doTag() usa findAncestorWithClass() para encontrar el controlador CacheTag de <jc:cache> que contiene el elemento de etiqueta <jc:dynamic>. Si no se encuentra o el almacenamiento en caché está deshabilitado, la expresión JSP se evalúa con JspUtils.eval() y se genera el valor. De lo contrario, doTag() genera una expresión sin valor.
paquete com.devsphere.articles.jspcache;
importar javax.servlet.jsp.JspException;
importar javax.servlet.jsp.tagext.SimpleTagSupport;
importar java.io.IOException;
la clase pública DynamicTag extiende SimpleTagSupport {
cadena privada expr;
setExpr vacío público (expr cadena) {
this.expr = expr;
} public void doTag() lanza JspException, IOException {
Salida de cadena = "${" + expr + "}";
Antepasado de CacheTag = (CacheTag) findAncestorWithClass(
esto, CacheTag.clase);
if (ancestro == null || !ancestor.isCacheEnabled())
salida = (Cadena) JspUtils.eval(
salida, String.class, getJspContext());
getJspContext().getOut().print(salida);
}}
Al analizar el código anterior, puede observar que <jc:cache> y <jc:dynamic> cooperan para lograr una solución lo más eficiente posible. Si hay caché disponible, el fragmento de página se coloca en el búfer junto con la expresión JSP generada por <jc:dynamic> y se le asigna el valor CacheTag. Si el almacenamiento en caché está deshabilitado, el almacenamiento en caché deja de tener sentido y <jc:cache> simplemente ejecuta su parte del cuerpo JSP, dejando que DynamicTag asigne valores a la expresión JSP. A veces es necesario deshabilitar el almacenamiento en caché, especialmente durante el proceso de desarrollo, cuando el contenido cambia y se vuelven a compilar las páginas JSP. Por supuesto, el almacenamiento en caché debe habilitarse en el entorno de producción después del desarrollo.
Resumir
Para desarrollar grandes aplicaciones de nivel empresarial, debería considerar el uso de un marco que admita mejores mecanismos de almacenamiento en caché en lugar de simplemente usar variables JSP. Pero sin duda es útil comprender la tecnología de personalización basada en EL API.