Usando o EJBContext getContextData – isso é seguro?

Estou planejando usar EJBContext para passar algumas propriedades da camada de aplicativo (especificamente, um bean acionado por mensagens) para um retorno de ciclo de vida de persistência que não pode ser diretamente injetado ou passado parâmetros (ouvinte de session no EclipseLink, retorno de chamada de ciclo de vida da entidade etc.) e esse retorno de chamada está recebendo o EJBContext via JNDI.

Isso parece funcionar, mas existem algumas dicas ocultas, como segurança de thread ou vida útil do object, que estão faltando? (Suponha que o valor da propriedade que está sendo passado seja imutável, como String ou Long.)

Código de Bean de Amostra

 @MessageDriven public class MDB implements MessageListener { private @Resource MessageDrivenContext context; public void onMessage(Message m) { context.getContextData().put("property", "value"); } } 

Então o callback que consome o EJBContext

 public void callback() { InitialContext ic = new InitialContext(); EJBContext context = (EJBContext) ic.lookup("java:comp/EJBContext"); String value = (String) context.getContextData().get("property"); } 

O que eu estou querendo saber é, posso ter certeza de que o conteúdo do mapa contextData só é visível para a invocação / thread atual? Em outras palavras, se dois encadeamentos estiverem executando o método de callback simultaneamente, e ambos procurarem um EJBContext partir do JNDI, eles estão realmente obtendo diferentes conteúdos do mapa contextData ?

E, como isso realmente funciona – o EJBContext retornado da pesquisa JNDI é realmente um object wrapper em torno de uma estrutura semelhante a ThreadLocal , em última análise?

Eu acho que, em geral, o contrato do método é permitir a comunicação entre interceptadores + contextos de serviço da Web e beans. Portanto, o contexto deve estar disponível para todo o código, desde que nenhum novo contexto de invocação seja criado . Como tal, deve ser absolutamente thread-safe.

A seção 12.6 da especificação EJB 3.1 diz o seguinte:

O object InvocationContext fornece metadados que permitem que os methods do interceptor controlem o comportamento da cadeia de invocação. Os dados contextuais não são compartilháveis ​​entre chamadas de método de negócios separadas ou events de retorno de chamada de ciclo de vida. Se os interceptores forem chamados como resultado da chamada em um nó de extremidade de serviço da web, o mapa retornado por getContextData será o MessageContext JAX-WS

Além disso, o método getContextData é descrito em 4.3.3:

O método getContextData permite que um método de negócios, um método de retorno de chamada de ciclo de vida ou um método de tempo limite recupere qualquer contexto de interceptor / webservices associado à sua chamada.

Em termos de implementação real, o JBoss AS faz o seguinte:

 public Map getContextData() { return CurrentInvocationContext.get().getContextData(); } 

Em que o CurrentInvocationContext usa uma pilha com base em uma lista encadeada local de encadeamento para estourar e empurrar o contexto de invocação atual.

Veja org.jboss.ejb3.context.CurrentInvocationContext . O contexto de invocação apenas cria um simples HashMap , como é feito em org.jboss.ejb3.interceptor.InvocationContextImpl

Glassfish faz algo semelhante. Ele também obtém uma chamada e faz isso a partir de um gerenciador de invocação , que também usa uma pilha baseada em uma lista de matriz de encadeamento local para estender e empurrar esses contextos de invocação novamente.

O JavaDoc para a implementação do GlassFish é especialmente interessante aqui:

Esta variável TLS armazena uma ArrayList. O ArrayList contém objects ComponentInvocation que representam a pilha de chamadas neste encadeamento. Acessos ao ArrayList não precisam ser sincronizados porque cada thread tem seu próprio ArrayList.

Assim como no JBoss AS, o GlassFish cria preguiçosamente um HashMap simples, neste caso em com.sun.ejb.EjbInvocation . Interessante no caso do GlassFish é que a conexão com o webservice é mais fácil de detectar na fonte.

Eu não posso ajudá-lo diretamente com suas perguntas sobre o EJBContext , uma vez que o método getContextData foi adicionado no JEE6 ainda não há muita documentação sobre ele.

No entanto, existe outra maneira de transmitir dados contextuais entre EJBs, interceptores e retornos de chamada de ciclo de vida usando o TransactionSynchronizationRegistry . O conceito e o código de amostra podem ser encontrados neste post de blog de Adam Bien .

javax.transaction.TransactionSynchronizationRegistry contém uma estrutura semelhante a um mapa e pode ser usada para passar o estado dentro de uma transação. Ele funciona perfeitamente desde o antigo J2EE 1.4 dias e é independente de thread.

Como um Interceptor é executado na mesma transação que o ServiceFacade, o estado pode ser definido em um método @AroundInvoke . O TransactionSynchronizationRegistry (TSR) pode ser diretamente injetado em um Interceptor.

O exemplo lá usa injeção @Resource para obter o TransactionSynchronizationRegistry , mas também pode ser InitialContext a partir do InitialContext assim:

 public static TransactionSynchronizationRegistry lookupTransactionSynchronizationRegistry() throws NamingException { InitialContext ic = new InitialContext(); return (TransactionSynchronizationRegistry)ic.lookup("java:comp/TransactionSynchronizationRegistry"); }