Explicação detalhada do cache de conteúdo dinâmico em JSP 2.0
Autor:Eve Cole
Data da Última Atualização:2009-07-03 16:56:34
O cache de conteúdo é uma das técnicas de otimização mais comuns em aplicações web e pode ser facilmente implementada. Por exemplo, você pode usar uma tag JSP personalizada - vamos chamá-la de <jc:cache> - com <jc:cache> e </jc:cache> para encapsular cada fragmento de página que precisa ser armazenado em cache. Qualquer tag personalizada pode controlar quando a parte que ela contém (ou seja, o fragmento de página pré-empacotado) é executada e os resultados de saída dinâmica podem ser capturados. A tag <jc:cache> permite que um contêiner JSP (como o Tomcat) gere conteúdo apenas uma vez, como variáveis JSP de todo o aplicativo, para armazenar cada fragmento de cache. Cada vez que a página JSP é executada, a tag personalizada carrega o fragmento da página em cache sem a necessidade de executar o código JSP novamente para gerar a saída. Como parte do projeto Jacarta, a biblioteca de tags foi desenvolvida utilizando esta tecnologia. Funciona bem quando o conteúdo em cache não precisa ser personalizado para cada usuário ou solicitação.
Este artigo aprimora a técnica descrita acima usando JSP 2.0 Expression Language (EL), permitindo que páginas JSP personalizem o conteúdo armazenado em cache para cada solicitação e usuário. Os fragmentos de página de cache podem conter expressões JSP que não são avaliadas pelo contêiner JSP. Os valores dessas expressões são determinados por tags personalizadas sempre que a página é executada. Portanto, a criação de conteúdo dinâmico é otimizada, mas os fragmentos armazenados em cache podem conter partes do conteúdo gerado por cada solicitação usando a linguagem de expressão JSP nativa. Com a ajuda da API JSP 2.0 EL, os desenvolvedores Java podem tornar isso possível usando linguagens de expressão.
Cache de conteúdo versus cache de dados O cache de conteúdo não é a única opção. Por exemplo, os dados extraídos de um banco de dados também podem ser armazenados em cache. Na verdade, o cache de dados pode ser mais eficiente, pois as informações armazenadas não contêm marcação HTML e requerem menos memória. Em muitos casos, entretanto, o cache na memória é mais fácil de implementar. Suponha que, em um determinado caso, um aplicativo consista em um grande número de objetos de transação, ocupe recursos importantes da CPU, gere dados complexos e use páginas JSP para apresentar os dados. Tudo estava funcionando bem até que um dia a carga no servidor aumentou repentinamente e foi necessária uma solução de emergência. Neste momento, estabelecer uma camada de cache entre o objeto de transação e a camada de apresentação é uma solução muito boa e eficaz. Mas as páginas JSP que armazenam conteúdo dinâmico em cache devem ser modificadas com muita rapidez e facilidade. Em relação à simples edição de páginas JSP, as alterações na lógica de negócios do aplicativo geralmente exigem mais trabalho e testes. Além disso, se uma página agregar informações de diversas fontes compostas, a camada Web precisará apenas de uma pequena quantidade de alterações; O problema é que o espaço de cache precisa ser liberado quando as informações armazenadas em cache ficam obsoletas, e o objeto de transação deve saber quando isso acontece. No entanto, ao optar por implementar cache de conteúdo ou cache de dados, ou outras técnicas de otimização, há muitos fatores que devem ser considerados e, às vezes, há requisitos especiais para o programa que está sendo desenvolvido.
O cache de dados e o cache de conteúdo não são necessariamente mutuamente exclusivos; eles podem ser usados juntos. Por exemplo, em um aplicativo baseado em banco de dados, os dados extraídos do banco de dados e o HTML que os apresenta são armazenados em cache separadamente. Isso é um pouco semelhante ao uso de JSP para gerar modelos em tempo real. As técnicas de API baseadas em EL discutidas neste artigo ilustram como usar JSP EL para carregar dados em modelos de renderização.
Armazenando conteúdo dinâmico em cache usando variáveis JSP Sempre que você implementar um mecanismo de cache, será necessário um método para armazenar objetos de cache. Neste artigo, objetos do tipo String estão envolvidos. Uma opção é usar uma estrutura de cache de objetos ou usar mapas Java para implementar um esquema de cache personalizado. JSP já possui os chamados "atributos com escopo definido" ou "variáveis JSP" para fornecer mapeamento de objeto de ID, que é o que o mecanismo de cache exige. Para usar o escopo de página ou solicitação, isso não faz sentido, enquanto no escopo do aplicativo este é um bom local para armazenar conteúdo em cache, pois é compartilhado por todos os usuários e páginas. O escopo da sessão também pode ser usado quando é necessário um cache separado para cada usuário, mas isso não é muito eficiente. A biblioteca de tags JSTL pode ser usada para armazenar conteúdo em cache, usando variáveis JSP conforme mostrado no exemplo a seguir:
<%@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl/core " %><c:if test="${empty cachedFragment}">
<c:set var="cachedFragment" escopo="application">
...
</c:set></c:if>
O fragmento de página em cache gera os resultados usando a seguinte instrução:
${applicationScope.cachedFragment}
O que acontece quando o fragmento de cache precisa ser customizado para cada solicitação? Por exemplo, se quiser incluir um contador, você precisará armazenar em cache dois fragmentos:
<%@ 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" escopo="application">
...
</c:set></c:if><c:if test="${empty cachedFragment2}">
<c:set var="cachedFragment2" escopo="application">
...
</c:set></c:if>
Você pode usar a seguinte instrução para gerar o conteúdo do cache:
${cachedFragment1} ${contador} ${cachedFragment2}
Com a ajuda de uma biblioteca de tags dedicada, o armazenamento em cache de fragmentos de páginas que requerem personalização torna-se extremamente fácil. Conforme mencionado acima, o conteúdo armazenado em cache pode ser delimitado por tags de abertura (<jc:cache>) e tags de fechamento (</jc:cache>). Cada customização pode ser expressa usando outra tag (<jc:dynamic expr="..."/>) para gerar uma expressão JSP (${...}). O conteúdo dinâmico é armazenado em cache usando expressões JSP e atribuído sempre que o conteúdo armazenado em cache é emitido. Você pode ver como isso é conseguido na seção abaixo. Counter.jsp armazena em cache um fragmento de página contendo um contador. O contador aumentará automaticamente em 1 sempre que o usuário atualizar a página.
<%@ 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>
Variáveis JSP são fáceis de usar e são uma boa solução de cache de conteúdo para aplicativos Web simples. No entanto, se o aplicativo gerar muito conteúdo dinâmico, não ter controle sobre o tamanho do cache será definitivamente um problema. Uma estrutura de cache dedicada pode fornecer uma solução mais poderosa, permitindo monitoramento de cache, limitando o tamanho do cache, controlando a política de cache, etc...
Usando a API de linguagem de expressão JSP 2.0
Os contêineres JSP (como o Tomcat) avaliam expressões em páginas JSP usando a API EL e podem ser usados pelo código Java. Isso permite o desenvolvimento fora de páginas da Web usando JSP EL, por exemplo, em arquivos XML, recursos baseados em texto e scripts personalizados. A API EL também é útil quando você precisa controlar quando as expressões em uma página da Web são atribuídas ou quando são escritas expressões relacionadas a elas. Por exemplo, fragmentos de página em cache podem conter expressões JSP personalizadas, e a API EL será usada para atribuir ou reavaliar essas expressões sempre que o conteúdo em cache for emitido.
O artigo fornece um programa de exemplo (consulte a seção de recursos no final do artigo). Este aplicativo contém uma classe Java (JspUtils) e um método eval() na classe. Este método possui três parâmetros: Expressão JSP, o tipo esperado. da expressão e um objeto de contexto JSP. O método Eval() obtém o ExpressionEvaluator do contexto JSP e chama o método avaliar(), passando a expressão, o tipo esperado da expressão e uma variável obtida do congtext JSP. O método JspUtils.eval() retorna o valor da expressão.
pacote 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;public class JspUtils {
avaliação de objeto estático público (
String expr, tipo de classe, JspContext jspContext)
lança JspException {
tentar {
se (expr.indexOf("${") == -1)
expr de retorno;
Avaliador ExpressionEvaluator
= jspContext.getExpressionEvaluator();
retornar avaliador.avaliar(expr, tipo,
jspContext.getVariableResolver(), null);
} catch (ELException e) {
lançar nova JspException(e);
}
}
...}
Nota: JspUtils.eval() encapsula principalmente o ExpressionEvaluator padrão. Se expr não contiver ${, a API JSP EL não será chamada porque não há expressão JSP.
Criando um arquivo descritor de biblioteca de tags (TLD) Uma biblioteca de tags JSP requer um arquivo descritor de biblioteca de tags (TLD) para customizar a nomenclatura de tags, seus atributos e as classes Java que operam na tag. jspcache.tld descreve duas tags personalizadas <jc:cache> tem dois atributos: o id do fragmento da página em cache e o escopo JSP – o escopo do conteúdo da página JSP que sempre precisa ser armazenado. <jc:dynamic> possui apenas um atributo, a expressão JSP que deve ser avaliada toda vez que o fragmento de cache é gerado. O arquivo TLD mapeia essas duas tags personalizadas para as classes CacheTag e DynamicTag da seguinte forma:
<?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"
versão="2.0">
<tlib-versão>1.0</tlib-versão>
<nome curto>jc</nome curto>
<uri>http://devsphere.com/articles/jspcache</uri>
<tag>
<nome>cache</nome>
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
<body-content>sem script</body-content>
<atributo>
<nome>id</name>
<required>verdadeiro</required>
<rtexprvalue>verdadeiro</rtexprvalue>
</atributo>
<atributo>
<name>escopo</name>
<obrigatório>falso</obrigatório>
<rtexprvalue>falso</rtexprvalue>
</atributo>
</tag>
<tag>
<name>dinâmico</name>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>vazio</body-content>
<atributo>
<name>expr</name>
<required>verdadeiro</required>
<rtexprvalue>falso</rtexprvalue>
</atributo>
</tag></taglib>
O arquivo TLD está incluído no arquivo descritor da aplicação Web (web.xml). Esses cinco arquivos também contêm um parâmetro inicial que indica se o cache está disponível.
<?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"
versão="2.4">
<parâmetro de contexto>
<param-name>com.devsphere.articles.jspcache.enabled</param-name>
<param-value>verdadeiro</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>
Entenda o mecanismo de funcionamento de <jc:cache> O contêiner JSP cria uma instância CacheTag para cada tag <jc:cache> na página JSP para processá-la. O container JSP é responsável por chamar os métodos setJsp(), setParent() e setJspBody(), que é a classe CacheTag herdada de SimpleTagSupport. O contêiner JSP também chama o método setter para cada atributo da tag manipulada. Os métodos SetId() e setScope() armazenam o valor do atributo em um campo privado. Este valor foi inicializado com o valor padrão usando o construtor CacheTag().
pacote 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;
classe pública CacheTag estende SimpleTagSupport {
String final estática pública CACHE_ENABLED
= "com.devsphere.articles.jspcache.enabled";
id de string privado;
escopo interno privado;
cache booleano privadoEnabled; public CacheTag() {
id = nulo escopo = PageContext.APPLICATION_SCOPE;
} public void setId(ID da string) {
isto.id = id;
} public void setScope(Escopo da string) {
this.scope = JspUtils.checkScope(escopo);
}
...}
O método setScope() chama JspUtils.checkScope() para verificar o valor da propriedade do escopo que foi convertido do tipo String para int.
... classe pública JspUtils {
...
public static int checkScope(String escopo) {
if ("página".equalsIgnoreCase(escopo))
retornar PageContext.PAGE_SCOPE;
senão if ("solicitação".equalsIgnoreCase(escopo))
retornar PageContext.REQUEST_SCOPE;
senão if ("sessão".equalsIgnoreCase(escopo))
retornar PageContext.SESSION_SCOPE;
senão if ("aplicativo".equalsIgnoreCase(escopo))
retornar PageContext.APPLICATION_SCOPE;
outro
lançar new IllegalArgumentException(
"Escopo inválido: " + escopo);
}}
Assim que a instância CacheTag estiver pronta para operar na tag, o contêiner JSP chama o método doTag() e obtém o contexto JSP usando getJspContext(). Este objeto é convertido como PageContext, para que o método getServletContext() possa ser chamado. O contexto do servlet é utilizado para obter o valor do parâmetro de inicialização, que indica se o mecanismo de cache está habilitado. Se o cache estiver habilitado, doTag() tenta obter o fragmento da página em cache usando os valores dos atributos id e scope. Se o fragmento da página não tiver sido armazenado em cache, doTag() usa getJspBody().invoke() para executar o código JSP encapsulado por <jc:cache> e </jc:cache>. A saída gerada pelo corpo JSP é armazenada em buffer em um StringWriter e recuperada pelo método toStirng(). Desta forma, doTag() chama o método setAttribute() do contexto JSP para criar uma nova variável JSP. Esta variável controla o conteúdo do cache que pode conter expressões JSP (${…}). Essas expressões são atribuídas por JspUtils.eval() antes de gerar o conteúdo usando jspContext.getOut().print(). Esse comportamento ocorre apenas quando o cache está ativado. Caso contrário, doTag() apenas executa o corpo JSP por meio de getJspBody().invoke(null) e o resultado da saída não é armazenado em cache.
...classe pública CacheTag estende SimpleTagSupport {
...
public void doTag() lança JspException, IOException {
JspContext jspContext = getJspContext();
Aplicativo ServletContext
= ((PageContext) jspContext).getServletContext();
String cacheEnabledParam
=aplicativo.getInitParameter(CACHE_ENABLED);
cacheEnabled=cacheEnabledParam!=nulo
&& cacheEnabledParam.equals("true");
if (cacheEnabled) {
String cachedOutput
= (String) jspContext.getAttribute(id, escopo);
if (cachedOutput == nulo) {
Buffer StringWriter = new StringWriter();
getJspBody().invoke(buffer);
cachedOutput = buffer.toString();
jspContext.setAttribute(id, cachedOutput, escopo);
} String avaliadaOutput = (String) JspUtils.eval(
cachedOutput, String.class, jspContext);
jspContext.getOut().print(avaliadoOutput);
} outro
getJspBody().invoke(null);
}
...}
Observe que uma única chamada JspUtils.eval() avalia todas as expressões ${…}. Porque um texto contendo um grande número de estruturas ${...} também é uma expressão. Cada fragmento de cache pode ser processado como uma expressão JSP complexa.
O método IsCacheEnabled() retorna o valor de cacheEnabled, que foi inicializado por doTag().
...classe pública CacheTag estende SimpleTagSupport {
... public boolean isCacheEnabled() {
retornar cacheEnabled;
}}
A tag <jc:cache> permite que os desenvolvedores de páginas escolham o ID dos fragmentos de páginas em cache. Isso permite que fragmentos de páginas em cache sejam compartilhados por diversas páginas JSP, o que é útil ao reutilizar código JSP. Mas ainda é necessário algum protocolo de nomenclatura para evitar possíveis conflitos. Este efeito colateral pode ser evitado modificando a classe CacheTag para incluir a URL dentro do ID automático.
Entendendo o que <jc:dynamic> está fazendo Cada <jc:dynamic> é tratado por uma instância da classe DynamicTag e o método setExpr() armazena o valor do atributo expr em um campo privado. O método DoTag() cria uma expressão JSP e adiciona o prefixo ${ e o sufixo } ao valor do atributo expr. Então, doTag() usa findAncestorWithClass() para encontrar o manipulador CacheTag de <jc:cache> que contém o elemento de tag <jc:dynamic>. Se não for encontrado ou o cache estiver desabilitado, a expressão JSP será avaliada com JspUtils.eval() e o valor será gerado. Caso contrário, doTag() gera uma expressão sem valor.
pacote com.devsphere.articles.jspcache;
importar javax.servlet.jsp.JspException;
importar javax.servlet.jsp.tagext.SimpleTagSupport;
importar java.io.IOException;
classe pública DynamicTag estende SimpleTagSupport {
string privada expr;
public void setExpr(String expr) {
isto.expr = expr;
} public void doTag() lança JspException, IOException {
Saída de string = "${" + expr + "}";
CacheTag ancestral = (CacheTag) findAncestorWithClass(
isso, CacheTag.class);
if (ancestral == nulo || !ancestor.isCacheEnabled())
saída = (String) JspUtils.eval(
saída, String.class, getJspContext());
getJspContext().getOut().print(saída);
}}
Analisando o código acima, você pode notar que <jc:cache> e <jc:dynamic> cooperam para alcançar uma solução o mais eficiente possível. Se o cache estiver disponível, o fragmento da página será colocado no buffer junto com a expressão JSP gerada por <jc:dynamic> e atribuído o valor CacheTag. Se o cache estiver desabilitado, o cache perde o sentido e <jc:cache> simplesmente executa sua parte do corpo JSP, deixando DynamicTag atribuir valores à expressão JSP. Às vezes, a desativação do cache é necessária, especialmente durante o processo de desenvolvimento, quando o conteúdo é alterado e as páginas JSP são recompiladas. É claro que o cache deve ser habilitado no ambiente de produção após o desenvolvimento.
Resumir
Para desenvolver grandes aplicativos de nível empresarial, você deve considerar o uso de uma estrutura que suporte melhores mecanismos de armazenamento em cache, em vez de usar apenas variáveis JSP. Mas é sem dúvida útil compreender a tecnologia de customização baseada na API EL.