Maneira correta de procurar o EJB local no websphere – Obtendo ClassCastException

Eu tenho um EJB que é exposto por interfaces locais e remotas

package com.sam.enqueue; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Singleton; @Singleton @Local(SamEnqueueLocal.class) @Remote(SamEnqueueRemote.class) public class SamEnqueue implements SamEnqueueRemote, SamEnqueueLocal { } // remote interface package com.sam.enqueue; import javax.ejb.Remote; @Remote public interface SamEnqueueRemote { } // local interface package com.sam.enqueue; @Local public interface SamEnqueueLocal { } 

Meu contêiner de aplicativos é o websphere 8.0 e não estou substituindo os nomes JNDI padrão que o servidor atribui. Durante a boot do servidor, recebo as seguintes ligações padrão nos logs:

 CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejb/SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejblocal:SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueLocal CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejblocal:com.sam.enqueue.SamEnqueueLocal CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal 

A class de pesquisa é apenas uma class java simples em um EAR diferente no mesmo servidor com o seguinte código:

 Context ctx = new InitialContext(); Object local = ctx.lookup("java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal"); SamEnqueueLocal samEnqueue = (SamEnqueueLocal) local; 

A pesquisa está trabalhando com qualquer um dos três nomes JNDI para o local, mas não está sendo convertido em SamEnqueueLocal . O traço de exceção é:

 SystemErr R java.lang.ClassCastException: com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f incompatible with com.sam.enqueue.SamEnqueueLocal ... rest ommited 

Eu fiz uma biblioteca compartilhada e coloquei o esboço do destino EAR nele. A biblioteca é o caminho de class do EAR de pesquisa de origem com a política Classes loaded with local class loader first (parent last) . A biblioteca não está isolada. Se eu remover o stub, recebo um java.lang.ClassNotFoundException: com.sam.enqueue.SamEnqueueLocal conforme o esperado.

Atualizar:

Ao usar a injeção de dependência:

 @EJB(lookup="ejblocal:com.sam.enqueue.SamEnqueueLocal") private SamEnqueueLocal samEnqueueLocal; 

O erro que recebo é:

 javax.ejb.EJBException: Injection failure; nested exception is: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f Caused by: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f 

Então é basicamente o mesmo.

Você está obtendo java.lang.ClassCastException porque está recuperando uma referência a um EJB que existe em um carregador de classs diferente da unidade de implementação (ejb-jar, war, etc) que está tentando injetá-lo.

O uso de referências EJB locais entre aplicativos depende do fornecedor, se possível. Você poderá implantar seu bean SamEnqueue em um módulo EJB separado e tentar referenciá-lo por meio de um manifesto Class-Path: input de cada aplicativo. Certifique-se de que não haja cópias do SamEnqueueLocal no arquivo EAR.

Como alternativa, basta usar a interface SamEnqueueRemote .

Consulte o capítulo 8 da especificação Java EE para obter mais informações.

Consulte a seção “Visualizações do cliente local” do tópico Módulos EJB no centro de conhecimento:

A especificação EJB requer apenas que as visualizações do cliente local sejam suportadas para EJBs empacotados no mesmo aplicativo. Isso inclui residências locais, interfaces de negócios locais e a visualização sem interface. O WebSphere Application Server permite access a visualizações do cliente local para EJBs empacotados em um aplicativo separado com algumas restrições

  • A interface local e todos os tipos de parâmetro, retorno e exceção usados ​​pela interface local devem estar visíveis para o carregador de classs do aplicativo de chamada e do aplicativo EJB de destino. Você pode garantir isso usando uma biblioteca compartilhada associada a um carregador de classs do servidor ou usando uma biblioteca compartilhada isolada associada a ambos os aplicativos. Leia o tópico Criando bibliotecas compartilhadas para mais informações.

A partir do link fornecido pela resposta do bkail , estes são os passos que segui para que funcione.

  1. Retire as interfaces Local e Remota, por exemplo , SamEnqueueRemote e SamEnqueueLocal do meu jar EJB de origem e empacote em um arquivo jar separado. Apesar de apenas tirar a interface local também funcionará.
  2. Faça uma biblioteca compartilhada e coloque este flask nela. A biblioteca compartilhada deve ser isolada para que a mesma versão da class seja carregada pelo chamador e pelo responsável.
  3. No EAR do chamador, obtenha uma referência à interface local com uma pesquisa ou injeção.
  4. Implante o chamador e o chamado para o servidor e certifique-se de include a biblioteca compartilhada no caminho de class de ambos os EARs.

Uma das abordagens mencionadas neste link é semelhante.

Uma maneira de evitar isso é usar uma interface remota. Como Paul mencionou, há otimização que acontece quando o chamador e o chamado estão na mesma JVM, portanto, não é tão caro quanto se eles estivessem em JVMs separadas. O ORB possui um mecanismo de carregamento de class que garante que as classs para o chamador e o responsável pela chamada sejam carregadas usando carregadores de class compatíveis com cada lado da chamada.

Opção 2, incluindo o jarro ejb dentro do novo ouvido, não vai resolver o problema. Mesmo que as classs estejam disponíveis em ambos os carregadores de class, o object transmitido por referência do chamado de retorno para o chamador ainda será instanciado usando o carregador de class do outro aplicativo e não será atribuível a tipo. Mesmo com a opção 3.

A segunda maneira de fazê-lo funcionar é colocar as classs usadas pelo chamador e pelo responsável em uma “biblioteca compartilhada do WAS” e configurar ambos os aplicativos para usar essa biblioteca compartilhada. O assunto das bibliotecas compartilhadas e como configurá-las é descrito na documentação do WAS InfoCenter … pesquise “biblioteca compartilhada”.

A terceira maneira de fazê-lo funcionar, que é o menos desejável dos três, é alterar a política do carregador de class do servidor WAS para “um classloader por servidor”. O padrão, como mencionei no início, é “um classloader por aplicativo (arquivo EAR)”. Mudar para um classloader por servidor garante que tudo seja carregado pelo mesmo carregador de class e, portanto, seja compatível com o tipo, mas priva seus aplicativos dos benefícios de isolamento / segurança que vêm de ter cada aplicativo em seu próprio carregador de class.