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 à edição simples 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:definir></c:se> |
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="contador" escopo="sessão" valor="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:definir></c:se> |
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 do cache pode ser encapsulado por tags iniciais (<jc:cache>) e tags finais (</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="contador" escopo="sessão" valor="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 o
contêiner JSP da API de linguagem de expressão JSP 2.0 (como Tomcat) para aplicar as expressões da API EL no JSP page recebem valores 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;classe pública JspUtils { avaliação de objeto estático público (String expr, tipo de classe, JspContext jspContext) lançaJspException { tentar { if (expr.indexOf("${") == -1) return expr; 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, ou seja, a expressão JSP deve ser atribuída toda vez que o fragmento de cache for gerado. O arquivo TLD mapeia essas duas tags personalizadas para as classes CacheTag e DynamicTag da seguinte forma:
<?xml versão="1.0" codificação="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-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>scriptless</body-content> <atributo> <nome>id</nome> <obrigatório>verdadeiro</obrigatório> <rtexprvalue>true</rtexprvalue> </atributo> <atributo> <nome>escopo</nome> <obrigatório>falso</obrigatório> <rtexprvalue>false</rtexprvalue> </atributo> </tag> <tag> <nome>dinâmico</nome> <tag-class> com.devsphere.articles.jspcache.DynamicTag</tag-class> <conteúdo-corpo>vazio</conteúdo-corpo> <atributo> <nome>expr</nome> <obrigatório>verdadeiro</obrigatório> <rtexprvalue>false</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 versão="1.0" codificação="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> <valor-parâmetro>verdadeiro</valor-parâmetro> </parâmetro-contexto> <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 herda 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ático público CACHE_ENABLED = "com.devsphere.articles.jspcache.enabled"; private String id; 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 (escopo de string) { 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; caso contrário, lance uma nova 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= application.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); } senão 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().
O método IsCacheEnabled() retorna o valor de cacheEnabled, que foi inicializado por doTag(). ...classe pública CacheTag estende SimpleTagSupport { ... public boolean isCacheEnabled() { return 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; público void setExpr(String expr) { isto.expr = expr; } public void doTag() lança JspException, IOException { Saída de string = "${" + expr + "}"; Ancestral CacheTag = (CacheTag) findAncestorWithClass (este, 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 é colocado no buffer junto com a expressão JSP gerada por <jc:dynamic> e recebe um 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.
Resumo
O cache de conteúdo é um método muito fácil de usar para melhorar o desempenho de aplicativos web. Este artigo se concentra no uso da linguagem de expressão JSP para personalizar o conteúdo do cache para cada usuário ou solicitação. As bibliotecas de tags apresentadas brevemente neste artigo são adequadas para aplicativos Web pequenos e podem melhorar o desempenho de aplicativos de médio porte. Para desenvolver aplicativos de nível empresarial em larga escala, você deve considerar o uso de uma estrutura que suporte melhores mecanismos de cache, em vez de apenas usar variáveis JSP.