Mostrando postagens com marcador ClassLoader. Mostrar todas as postagens
Mostrando postagens com marcador ClassLoader. Mostrar todas as postagens

terça-feira, 4 de agosto de 2020

Criando shell script para executar aplicações Java no Linux com dependências

INTRODUÇÃO


Nessa publicação explicaremos como criar o arquivo .sh para iniciar uma aplicação Java com as suas dependências (Pastas libs contendo Jars, por exemplo). Para isto, vamos criar uma aplicação simples chamada "ExemploJava".


CONFIGURAÇÃO


Essa aplicação terá uma pasta chamada "libs" para conter os jars de bibliotecas de terceiros que a aplicação pode usar (ou não) e será referenciada no classpath da aplicação Java.

Outra pasta chamada de "bin" conterá os pacotes e classes do projeto. Por exemplo, se tivermos uma classe fictícia chamada "A" dentro do pacote "a.b.c", portanto o nome completo da classe será "a.b.c.A". E dentro da pasta "bin", teremos a pasta "a" e dentro desta a pasta "b". E por último, dentro da pasta "b", teremos a pasta "c" que conterá o arquivo "A.java" definindo a classe pública "A".

Estas duas pastas estarão dentro da pasta "ExemploJava" e o script .sh ("exemploJava.sh" por exemplo) estará dentro desta pasta.

Para efeito prático, vamos adotar a classe "A" como a classe com o método main (inicializadora da aplicação Java).

Portanto, o conteúdo do arquivo .sh será:

java -Dfile.encoding=UTF-8 -cp ".:bin/:libs/*:" a.b.c.A 


Aonde temos:

-Dfile.encoding=UTF-8
aonde informa a codificação usada, muito recomendável usar unicode no UTF-8 pois é uma codificação internacional e preparada para lidar com diversos alfabetos e idiomas.


-cp ".:bin/:libs/*:"
aonde informamos as pastas, jars e arquivos .class que estarão no classpath. Estando no classpath, nossa aplicação pode carregar e usar as classes. Se não estiver no classpath, pode dar erros em tempo de execução, lançado exceções dos tipos "java.lang.ClassNotFoundException" ou "java.lang.NoClassDefFoundError" por exemplo. Além disso, se forem colocados paths (caminhos) errados, ao rodar a aplicação não dará erro a priori (por causa desses paths sem jars ou arquivos .class), mas quando a classe Main for executada e for carregando as dependências, pode chegar numa classe que não estará no classpath. Portanto, muito cuidado para colocar o caminho correto para que as dependências dentro desses caminhos sejam carregadas corretamente para dentro da JVM.

Nesse exemplo, usamos a pasta bin para conter os arquivos .class ou recursos. Mas também é possível empacotar tudo num jar e colocar nesse argumento diretamente (":meuarquivo.jar") ou colocar esse jar na pasta "bin" ou "libs".
Lembrando também que os dois-pontos ":" é o separador de paths (caminhos) entre um jar ou diretório do outro. O sinal ponto "." representa o diretório atual que o script está sendo rodado.


a.b.c.A
É o nome completo para a classe Main que será iniciada pela JVM no comando "java".
Sem essa classe Main, o comando "java" abortará, mostrando uma mensagem de erro dizendo que não foi informado uma classe com o método estático "void main(String args[])".


OBSERVAÇÃO


Muito importante, depois da classe main "A", deverá ter um espaço em branco!
Caso contrário, ao executar o script sh, dará erros dizendo que a classe "a.b.c.A" não foi localizada e nem carregada! Pois, o shell script fornecerá como parâmetro a classe de nome completo "a.b.c.A\n" com a QUEBRA DE LINHA inclusa! E logicamente, no nosso classpath (formado pelo núcleo do Java e mais as pastas informadas no parâmetro "cp") essa classe de nome completo "a.b.c.A\n" não existirá! E pior, difícil de localizar esse tipo de erro, pois a quebra de linha "\n" fica invisível no terminal!

sexta-feira, 11 de outubro de 2013

Ordem de carregamento de classes / jars no JBoss AS 7


INTRODUÇÃO

Nesse blog escrevi sobre isolamentos de classloaders no JBoss AS 7.
Vejam nesse link:

http://www.josepojr.com/2013/01/isolamento-de-classloaders-no-jboss-as-7.html

Mas gostaria de complementar com mais explicações sobre a ordem usada no carregamento de classes / jars no JBoss AS 7.


CONCEITOS

1 - Só criar módulos no JBoss 7 se for alguma API que será usada em vários projetos (WAR ou EAR) que serão implantados (deploy) no JBoss 7.

2 - Dentro de um EAR, pode-se colocar os jars dentro dos seus respectivos subprojetos. Ou seja, se no seu EAR tem dois wars e um jar (Projeto EJB), um jar que será usado em somente um war, pode ser colocado dentro da pasta WEB-INF/lib desse respectivo war. Agora, se o seu jar é usado em todos os subprojetos do ear, usar dentro do diretório EAR/lib.

3 - O JBoss 7 tem uma regra de procedência para decidir qual classe de qual jar carregar primeiro. Veja em:

https://docs.jboss.org/author/display/AS71/Class+Loading+in+AS7


Resumindo a regra usada, de acordo com a prioridade:

1º Carregar classes de sistemas do JRE e JavaEE (especificação).
2º Dependências declaradas explicitamente no arquivo jboss-deployment-structure.xml
3º Classes / jars internos dos subprojetos como WEB-INF/classes ou WEB-INF/lib para um projeto WAR.
4º Jars contidos na pasta EAR/lib do seu projeto EAR.


Essas regras são muito importantes para o desenvolvimento de aplicações no JBoss AS 7. Se sua classe estiver no mesmo pacote e mesmo nome (exemplo meupacote.MinhaClasse) e estiver em dois lugares como WEB-INF/lib e EAR/lib, o JBoss AS 7 carregará a classe que achará primeiro. E pela regra da prioridade, pegará a classe MinhaClasse que está no jar do subprojeto WAR pasta WEB-INF/lib. Se a classe MinhaClasse estiver também em um outro jar da pasta EAR/lib, a classe nesse pacote não será carregada.


CONCLUSÃO

A equipe de projeto e desenvolvimento do JBoss AS 7, tiveram que elaborar algum mecanismo para definir em casos de "dúvidas" qual classe será carregada, no caso de existirem classes como mesmo nome e pacote dentro do servidor JBoss AS 7. Essas regras visam tomar decisões para em tempo de execução, o JBoss AS 7 resolver o impasse e escolher uma classe dentro de um conjunto de N classes de mesmo pacote e nome. Claro que nesse cenário estamos lidando com o pior caso, de temos a mesma classe e no mesmo pacote e M versões distintas e rodando no mesmo servidor de aplicações JBoss AS 7.



sábado, 26 de janeiro de 2013

Isolamento de classloaders no JBoss AS 7


INTRODUÇÃO

Nesse artigo vamos mostrar o isolamento de classloaders no JBoss AS 7. Esse isolamento permite eliminar erros chamados de “Classloader Hell” que pode ocorrer nas seguintes situações:

  • Se em tempo de compilação usarmos uma biblioteca de terceiros, como a Apache Commons na versão 1.2.7 (por exemplo), mas em tempo de execução no servidor de aplicações a biblioteca usada estiver na versão 1.2.6 e classe em questão não possuir o método que existe na versão 1.2.7 pode levar a JVM do servidor a lançar a exceção java.lang.NoSuchMethodException.
  • Ou então, se a biblioteca Apache Commons usada em tempo de execução for de versão diferente da versão usada em tempo de compilação, mas os métodos usados na aplicação existem nas duas versões da biblioteca, isso pode levar a comportamentos anêmicos, pois no desenvolvimento usamos uma implementação da versão da biblioteca, mas na execução, usamos outra implementação e mesmo que aparentemente não ocorra exceções como java.lang.NoSuchMethodException, java.lang.ClassNotFoundException, java.lang.NoClassDefFoundError e outras exceções semelhantes, o comportamento da aplicação será imprevisível.
  • Como os classloaders seguem uma hierárquia na qual, um classloader delega a chamada para resolver a classe para outro classloader, temos o problema de um determinado classloader carregar uma classe de uma biblioteca e outra classe de outra biblioteca, que possuam os nomes completos qualificados iguais. Isto é, teremos aplicações que a principio, deveriam carregar todas as classes da mesma biblioteca, mas na realidade (em tempo de execução), há um carregamento parcial de classes de um jar e outro carregamento parcial de outro jar, e assim, gerando um comportamento imprevisível.

Então, para eliminar esses problemas, a equipe de desenvolvimento do JBoss AS, resolver reescrever o servidor aplicações JBoss AS para eliminar esses erros recorrentes de classloaders na versão 7.x.


PREPARAÇÃO DO AMBIENTE

Para testar / analisar esse isolamento, vamos criar o projeto EAR de nome “TestEAR” e cujos módulos serão mostrados na figura a seguir.


Nesse figura temos o projeto “TestEAR” contendo 2 projetos EJB's que são “Test1EJB” e “Test2EJB”. E dois projetos web que são “Test1WAR” e “Test2WAR”. A seguir será mostrado o código do arquivo “application.xml” que define os módulos dentro do EAR.

<xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd" id="Application_ID" version="6">
<display-name>TestEAR</display-name>
<initialize-in-order>true</initialize-in-order>
<module>
<ejb>Test1EJB.jar</ejb>
</module>
<module>
<ejb>Test2EJB.jar</ejb>
</module>
<module>
<web>
<web-uri>Test1WAR.war</web-uri>
<context-root>Test1WAR</context-root>
</web>
</module>
<module>
<web>
<web-uri>Test2WAR.war</web-uri>
<context-root>Test2WAR</context-root>
</web>
</module>
</application>


E também, como estamos usando o servidor de aplicações JBoss AS 7, necessitamos usar o arquivo “jboss-deployment-structure.xml” que define as dependências entre módulos, exclusão de pacotes, uso de módulos do jboss, etc. Esse arquivo deve estar na pasta raiz do projeto EAR ou na pasta META-INF do projeto EAR junto com o arquivo “application.xml”. Esse arquivo será mostrado a seguir.

<xml version="1.0" encoding="UTF-8"?>

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.1">
<ear-subdeployments-isolated>false</ear-subdeployments-isolated>
<deployment>
<dependencies>
<module name="javax.api" />
<module name="deployment.TestEAR.ear.Test1EJB.jar"/>
<module name="deployment.TestEAR.ear.Test2EJB.jar"/>
<module name="deployment.TestEAR.ear.Test1WAR.war"/>
<module name="deployment.TestEAR.ear.Test2WAR.war"/>
</dependencies>
</deployment>
<sub-deployment name="Test1WAR.war">
<dependencies>
<module name="deployment.TestEAR.ear.Test1EJB.jar"/>
<module name="deployment.TestEAR.ear.Test2EJB.jar"/>
</dependencies>
<local-last value="true"/>
</sub-deployment>
<sub-deployment name="Test2WAR.war">
<dependencies>
<module name="deployment.TestEAR.ear.Test1EJB.jar"/>
<module name="deployment.TestEAR.ear.Test2EJB.jar"/>
</dependencies>
<local-last value="true"/>
</sub-deployment>

</jboss-deployment-structure>


Projeto “TestLib”

É um projeto Java simples que conterá uma classe utilitária para obter informações do classloader de outros projetos e também, para obter informações do seu próprio classloader. Na figura a seguir, será mostrado a estrutura do projeto “TestLib”.




Note que no projeto “TestLib”, só temos a classe TesteUtil cujo código será mostrado a seguir:

package utils;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class TesteUtil {

static {
StringBuilder str = new StringBuilder();

str.append("\nTesteUtil - Carregamento da classe - INICIO\n");
str.append(getInfoClassLoader());
str.append("\nTesteUtil - Carregamento da classe - FIM");

System.out.println(str.toString());
}

public static void main(String... strings) {

System.out.println("Classloader: ");
System.out.println(getInfoClassLoader());
}

public static String getInfoClassLoader(ClassLoader cl) {
Class<?> cls = cl.getClass();
StringBuilder str = new StringBuilder();
// Nome da classe e hashcode do objeto ClassLoader
str.append("\nClasse: " + cls.getName() + " - " + cl.hashCode() + "\n");

return str.toString();
}

public static String getInfoClassLoader() {
ClassLoader cl = TesteUtil.class.getClassLoader();

return getInfoClassLoader(cl);
}
}

Note que nessa classe “TesteUtil” temos um bloco anônimo estático justamente para que possamos obter informações do classloader do projeto “TestUtil” que será o mesmo do projeto EAR, pois esse projeto Java será mais um “jar” dentro da pasta “EAR/lib” do projeto EAR.


Projetos “Test1EJB” e “Test2EJB”

A estrutura do projeto “Test1EJB” será mostrado na figura a seguir, lembrando que o projeto “Test2EJB” é análogo (semelhante) ao projeto “Test1EJB” com a diferença nos nomes, ao invés de usar “Test1”, usa-se “Test2”, de outra maneira, só trocarmos o número “1” pelo número “2”.



Note que o projeto “Test1EJB” possui uma interface de nome “Teste1” que define um método para obter informações do classloader do projeto EJB. A seguir será mostrado o código dessa interface (arquivo “Teste1.java”).

package ejb;

import javax.ejb.Remote;

@Remote
public interface Teste1 {

public String getInfoClassLoader();
}

E para o EJB “Teste1Bean” que implementa a interface “Teste1” cujo código será mostrado a seguir:

package ejb;

import javax.annotation.PostConstruct;
import javax.ejb.Startup;
import javax.ejb.Stateless;

import utils.TesteUtil;

@Startup
@Stateless
public class Teste1Bean implements Teste1 {

@Override
public String getInfoClassLoader() {
return TesteUtil.getInfoClassLoader(this.getClass().getClassLoader());
}

@PostConstruct
public void printClassloader() {
StringBuilder str = new StringBuilder();
str.append("\nClassloader - PostConstruct1 - INICIO\n");
str.append(getInfoClassLoader());
str.append("Classloader - PostConstruct1 - FIM\n");

System.out.println(str.toString());
}
}


Projetos “Test1WAR” e “Test2WAR”

Esses são dois projetos web que usaram os projetos EJB's e o projeto Java “TestLib”. A estrutura do projeto “Test1WAR” será mostrado a seguir. Lembrando que o projeto “Test2WAR” é análogo (semelhante) ao projeto “Test1WAR”, portanto, toda configuração do “Test1WAR” vale para o projeto “Test2WAR” com a diferença nos nomes, basta trocar o número “1” por “2”.





Note que nesse projeto web, precisamos criar um ServiceLocator para facilitar a localização de EJB's dos outros projetos e também, criamos um Servlet para poder ser chamado via url no browser e cujo código fará chamadas nos projetos EJB's, Java e no próprio projeto web para obter informações a respeitos do classloaders dos projetos. A seguir será mostrado o código do ServiceLocator e depois, do Servlet “Test1WARServlet”.



package test.web.base;

import javax.naming.InitialContext;

public class ServiceLocator {

private InitialContext jndiContext;

private static ServiceLocator instance;

private static final String PREFIX_GLOBAL = "java:global/";

// private static final String PREFIX_APP = "java:app/";

private String earJndiName = "";

private ServiceLocator() {
try {
jndiContext = new InitialContext();

} catch (Exception e) {
e.printStackTrace();
}
}

public static ServiceLocator getInstance() {
if (instance == null) {
instance = new ServiceLocator();
}

return instance;
}

public String getEarJndiName() {
return earJndiName;
}

public void setEarJndiName(String earJndiName) {
this.earJndiName = earJndiName;
}

public Object get(final String jndiName) {
Object localHome = null;

try {
localHome = jndiContext.lookup(PREFIX_GLOBAL + earJndiName
+ jndiName);

} catch (Exception e) {
e.printStackTrace();
}

return localHome;
}
}



package test.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import test.web.base.ServiceLocator;
import utils.TesteUtil;
import ejb.Teste1;
import ejb.Teste2;

/**
* Servlet implementation class Test1WARServlet
*/
public class Test1WARServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public Test1WARServlet() {
super();

}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
StringBuilder str = new StringBuilder();
str.append("\nTest1WARServlet - INICIO\n");

str.append("\nClassloader - Lib\n");
str.append(TesteUtil.getInfoClassLoader());

str.append("\nClassloader - WAR\n");
str.append(TesteUtil.getInfoClassLoader(this.getClass()
.getClassLoader()));

ServiceLocator serviceLocator = ServiceLocator.getInstance();
serviceLocator.setEarJndiName("TestEAR/Test1EJB/");

Teste1 serviceTeste1 = (Teste1) serviceLocator.get("Teste1Bean");

str.append("\nClassloader - Teste1EJB\n");
str.append(serviceTeste1.getInfoClassLoader());

serviceLocator.setEarJndiName("TestEAR/Test2EJB/");

Teste2 serviceTeste2 = (Teste2) serviceLocator.get("Teste2Bean");

str.append("\nClassloader - Teste2EJB\n");
str.append(serviceTeste2.getInfoClassLoader());

str.append("\nTest1WARServlet - FIM");

System.out.println(str.toString());
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

}
}

A seguir, mostraremos o código do arquivo “web.xml”do projeto web. Nesse arquivo definimos o Servlet criado para que possa estar disponível para receber requisições HTTP.

<xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>Test1WAR</display-name>
<servlet>
<description></description>
<display-name>Test1WARServlet</display-name>
<servlet-name>Test1WARServlet</servlet-name>
<servlet-class>test.web.servlet.Test1WARServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Test1WARServlet</servlet-name>
<url-pattern>/teste1</url-pattern>
</servlet-mapping>
</web-app>


Com isso, para acessar o servlet, só clicar nesses links:



Lembrando que o IP do servidor é 127.0.0.1 e porta 8080. Caso esteja rodando servidor JBoss AS 7 em outra máquina, usar o IP dessa máquina para acessar o Servlet.


RESULTADOS

Ao executarmos o mesmo projeto EAR com ou sem isolação (tag ear-subdeployments-isolated do arquivo jboss-deployment-structure.xml), o JBoss AS 7 coloca uma instância de classloader (classe org.jboss.modules.ModuleClassLoader) para cada unidade de deploy dentro do EAR. Isto significa que o projeto EJB terá suas classes carregadas por uma instância de classloader, o projeto WAR terá suas classes carregadas por outra instância de classloader, pasta EAR/lib por outra instância e assim por diante para outros projetos EJB / WAR existentes dentro do EAR. Então, para melhor ilustração desse fato, veja na figura a seguir:



Consequentemente, essa tag (ear-subdeployments-isolated) apenas isola o uso das classes carregadas por cada unidade de deploy. Se essa tag estiver configurada como “true” teremos que definir as dependências entre unidades de deploy explicitamente no arquivo “jboss-deployment-structure.xml”, caso contrário, o servidor JBoss AS 7 lançará a exceção java.lang.ClassNotFoundException. Veja na figura a seguir como que as unidades de deploy se relacionam quando essa tag está com valor “true”.




Note que os projetos web “WAR1” e “WAR2” depende (pode acessar) os projetos EJB1, EJB2 e pasta EAR/Lib. Somente esse acesso é possível, se o EJB1 precisar acessar o EJB2, essa configuração precisa ser declarada explicitamente no arquivo “jboss-deployment-structure.xml”.

Se configurar essa tag (ear-subdeployments-isolated) como “false”, todas as unidades de deploy poderão usar as classes carregadas em outras unidades de deploy, sem restrição. Mas nessa configuração teremos que tomar cuidado para uma unidade de deploy não chamar classes de outras unidades de deploy que serão implantadas depois da unidade de deploy atual. Por exemplo se um projeto WAR chama uma classe do projeto EJB sendo que esse último projeto (unidade de deploy) não foi implantado ou será implantado depois do projeto WAR, então poderá ocorrer exceções como java.lang.ClassNotFoundException. Veja na figura a seguir como as unidades de deploy se relacionam quando essa tag possui o valor “false”.




Note que nessa configuração, todas as unidades de deploy (EJB1, EJB2, WAR1, WAR2 e EAR/Lib) podem acessar qualquer unidade de deploy no EAR.

Para evitar esse tipo de problema para o atributo configurado como “false”, deve-se configurar a tag “initialize-in-order” no arquivo application.xml com o valor “true”. Lembre-se que essa tag só está definida na versão 6 do JavaEE. Com essa configuração, forçaremos a implantação das unidades de deploy definidas no arquivo application.xml na ordem descrita nesse arquivo. Se não configurar essa tag com o valor “true”, o servidor JBoss AS 7 carregará as unidades de deploy na ordem que achar conveniente (digo, políticas implementadas dentro do JBoss AS 7, como por exemplo, tamanho dos módulos, tipo, prioridade de implantação entre módulos, etc) e não a ordem no documento xml.


REFERÊNCIAS




sábado, 7 de abril de 2012

Uso do JBoss Cache para aplicações JEE



1. INTRODUÇÃO

Nesse artigo vamos mostrar o uso da API JBoss Cache versão 3.10 apelido "Cascabel" numa aplicação JEE sobre o servidor de aplicações JBoss AS 5.1. A maior vantagem de se usar JBoss Cache reside no fato do JBoss Cache estar embutido no JBoss AS 5.1. Se for usar outra API de cache, terá que configurar todo o servidor de aplicações e com isso, correndo o risco de dar conflitos com outros jars da API do servidor JBoss. Além disso, possui uma API de fácil entendimento e uso, pois com poucos objetos e operações já estaremos tirando proveito de suas funcionalidades.


2. CONFIGURAÇÃO

Para ilustrar melhor o uso da API JBoss Cache, vamos criar um projeto EAR com o nome "CacheEAR" e outro projeto web com o nome "CacheWAR".


2.1. CRIAÇÃO DO PROJETO EAR

Para isso, vamos criar um projeto Ear (Enterprise Application) no IDE Eclipse 3.7.1 (ou pode ser outra IDE, desde que possua essa funcionalidade). Chamaremos o nosso projeto EAR de "CacheEAR". Veja nas figuras a seguir a criação do projeto EAR.




Observação

Precisa ter o JBOSS TOOLS instalado no Eclipse para criar o Runtime (Ambiente de Execução) do servidor de aplicações JBoss  5.1 (veja nas figuras a seguir).







2.2. CRIAÇÃO DO PROJETO WEB

Para isso, vamos criar um projeto web dinâmico (Que cria o arquivo WAR no JEE) no IDE Eclipse 3.7.1 (ou pode ser outra IDE, desde que possua essa funcionalidade). Chamaremos o nosso projeto web de "CacheWAR". Veja nas figuras a seguir a criação do projeto WAR.







2.3. CRIAÇÃO DO LISTENER DO CACHE


Para usar o JBoss Cache numa aplicação web da melhor maneira possível, precisamos criar um listener (ouvinte) para "escutar" o início e fim de execução de uma aplicação web. No início da aplicação web, o listener limpará o cache que ficou carregado num deploy da mesma aplicação anteriormente. Essa "limpeza" é necessária, pois se a aplicação web sofrer um redeploy, consequentemente, o novo deploy estará usando um classloader (fornecido pelo containter JEE) diferente do classloader dos objetos em cache (carregados pela mesma aplicação web no deploy antigo). Consequentemente, se a aplicação web "nova" (novo redeploy) estará com o classloader diferente dos objetos cacheados no deploy anterior, o que pode resultar na ocorrência da exceção ClassCastException. Para entender melhor a hierarquia de classloader de uma aplicação web, veja nas figuras a seguir.






Por isso temos que usar o listener (classe que implementará a interface javax.servlet.ServletContextListener) que possa limpar o cache do deploy antigo antes do início da aplicação web (que criará e usará o cache). Veja nas figuras a seguir o processo de criação do listener.





Cole o código abaixo dentro do corpo do método "void contextInitialized(ServletContextEvent arg0)" do listener criado (CacheListener).




// Toda vez que a aplicação web é iniciada, o cache apartir do caminho X
// precisa ser limpo, senão o cache pode buscar objetos carregados de
// outros

// deployers cujo classloader também será outro. Consequentemente,
// ocorrerão erros de ClassCastException ao tentar recuperar objetos do
// cache.

try {

javax.naming.Context ctx = new javax.naming.InitialContext();
org.jboss.cache.CacheManager cacheManager = (org.jboss.cache.CacheManager) ctx
.lookup("java:CacheManager");

org.jboss.cache.Cache<Object, Object> cache = cacheManager.getCache(
"standard-session-cache", true);


if (CacheStatus.INSTANTIATED.equals(cache.getCacheStatus())
|| CacheStatus.STOPPED.equals(cache.getCacheStatus())) {
cache.start();
}

org.jboss.cache.Fqn<String> f = org.jboss.cache.Fqn.fromElements("nivel1");

// Limpa cache.
cache.removeNode(f);

}  catch (Exception e) {
e.printStackTrace();
}
2.4. CRIAÇÃO DO POJO PARA SER USADO NO CACHE

Para ilustrar melhor o uso do cache, vamos criar uma classe simples java (POJO) para ser usada como objeto de cache para podermos entender melhor o uso da API JBoss Cache. Lembrando que é bom que essa classe implemente a interface java.io.Serializable para poder trafegar pela rede na aplicação web.
Veja o código a seguir:



import java.io.Serializable;

public class Pojo implements Serializable {

/**
*
*/
private static final long serialVersionUID = 5549023217127630221L;

private Long id;

public Pojo(Long id) {
this.id = id;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}
}



2.5. CRIAÇÃO DO SERVLET DO CACHE

Para testar o uso do cache, vamos criar um servlet que estende a classe javax.servlet.http.HttpServlet.
Esse servlet numa aplicação web é responsável por tratar as requisições web recebidas (seja no protocolo HTTP, FTP, etc) e enviar respostas as requisições (ou não). Nesse exemplo, ao enviar uma requisição para o servlet, o mesmo tentará buscar o objeto da classe Pojo (criada anteriormente) do cache. Na primeira requisição ao servlet, o objeto não existirá no cache, consequentemente, o servlet criará o objeto (da classe Pojo) e depois disso, adicionará no cache e imprimirá no console dados do objeto cacheado. Nas segundas requisições em diante, o objeto estará no cache, consequentemente, o servlet apenas imprimirá o objeto do cache, sem a necessidade da criação do objeto. Lembrando também que dependendo das configurações do JBoss Cache usado (veja no arquivo $JBOSS_HOME/server/all/deploy/cluster/jboss-cache-manager.sar/META-INF/jboss-cache-manager-jboss-beans.xml para ajuste fino do cache), o objeto cacheado tem um tempo de vida no cache, o que significa que o mesmo pode ficar no cache por um intervalo de tempo finito (definido nas configurações do JBoss Cache) e depois que esse tempo "estourou", o objeto cacheado é removido do cache. Para criar o servlet, veja nas figuras a seguir:







Abaixo temos o código para ser inserido no método "protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException" do CacheServlet criado.

try {

Context ctx = new InitialContext();
CacheManager cacheManager = (CacheManager) ctx
.lookup("java:CacheManager");

Cache<Object, Object> cache = cacheManager.getCache(
"standard-session-cache", true);


                        if (CacheStatus.INSTANTIATED.equals(cache.getCacheStatus())
|| CacheStatus.STOPPED.equals(cache.getCacheStatus())) {
cache.start();
}

Fqn<String> f = Fqn.fromElements("nivel1", "nivel2");

Pojo pojo = (Pojo) cache.get(f, new Long(10));

if (pojo == null) {
Fqn<String> afqn = Fqn.fromElements("nivel1", "nivel2");
Long id = 10l;
pojo = new Pojo(new Long(1000));

cache.put(afqn, id, pojo);

System.out.println("Adicionando objeto no cache");
} else {
System.out.println("Usando objeto no cache");
}

System.out.println(cache);
System.out.println(pojo);
} catch (Exception e) {
e.printStackTrace();
}



2.6. RESUMO DA CONFIGURAÇÃO


Abaixo estão listadas as configurações dos projetos criados. Todos os passos seguidos anteriormente já criam esses arquivos automaticamente, mas, por ventura a sua IDE usada deu algum conflito, falha que você não conseguiu criar os projetos adequadamente, então, veja as configurações abaixo:


Arquivo application.xml do projeto CacheEAR



<?xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" id="Application_ID" version="5">
  <display-name>CacheEAR </display-name>
  <module>
    <web>
      <web-uri>CacheWAR.war </web-uri>
      <context-root>CacheWAR </context-root>
   </web>
  </module>
</application>



Arquivo web.xml do projeto CacheWAR



<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>CacheWAR</display-name>
  <listener>
    <listener-class>CacheListener</listener-class>
  </listener>
  <servlet>
    <description></description>
    <display-name>CacheServlet</display-name>
    <servlet-name>CacheServlet</servlet-name>
    <servlet-class>CacheServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CacheServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>



Figura com a estrutura de diretórios dos dois projetos (CacheEAR e CacheWAR).




2.7. EXECUTANDO UM EXEMPLO

Abra qualquer navegador e digite a seguinte URL:

http://localhost:8080/CacheWAR/

Supondo que o servidor JBoss para aplicações web está usando a porta 8080 para receber requisições e também, estamos supondo o uso do servidor local (localhost) senão, terá que substituir o "localhost" pelo nome ou ip do servidor JBoss.

Nisso, o console do servidor JBoss mostrará uma mensagem sobre o objeto conforme figura a seguir:








Nessa primeira requisição do objeto ao cache, o mesmo não está no cache, então o servlet terá que instânciar o objeto e adicionar no cache. Agora, chamamos novamente o mesmo link (http://localhost:8080/CacheWAR/) e a mensagem mostrada sobre o objeto cacheado conforme mostra a figura a seguir:


Nota-se que o objeto instanciado anteriormente (primeira requisição do objeto ao cache) e o objeto cacheado são os mesmos objetos!! Isso mostra que o JBoss Cache guarda o objeto em cache conforme suas configurações (políticas de cache que o desenvolvedor pode alterá-las no arquivo $JBOSS_HOME/server/all/deploy/cluster/jboss-cache-manager.sar/META-INF/jboss-cache-manager-jboss-beans.xml).


3. CONCLUSÃO

O JBoss Cache permite o uso de cache de maneira rápida e fácil numa aplicação JEE usando como servidor de aplicações o JBoss AS 5.1. Embora no site do JBoss Cache cite que o projeto está em "Estado de manutenção" e que novas funcionalidades a princípio não serão incorporadas, existem muitas empresas que usam o JBoss AS 5.1 em produção e cujo custo de mudar o servidor de aplicações é proibitivo, fora o risco de desconfigurar a API padrão de caches (JBoss Cache 3.1) do servidor JBoss e configurar outra API, como a Infinispan ou Ehcache dentro do servidor JBoss.


4. REFERÊNCIAS

Download da última versão do IDE Eclipse

http://www.eclipse.org/downloads/

Download da última versão do JBOSS TOOLS

http://www.jboss.org/tools/download

JBoss Cache

http://www.jboss.org/jbosscache

http://www.ime.usp.br/~reverbel/SMA-06/Slides/seminarios/

Outras API's de cache (no Java)

http://ehcache.org/

http://www.jboss.org/infinispan