Esta semana eu descobri que misturar EJB3 e Generics não acontece sem alguns problemas. EJB3 é um bom exemplo de porque erasure nunca foi uma boa idéia e te proibe usar alguns idiomas comuns. Bom, felizmente não é o fim do mundo e tem como contornar sem muita dor.
Para entender o problema, vamos lembrar um pouco como Generics funciona com herança, temos as interfaces Foo<T> e Bar extends Foo<String>, os métodos de Foo que dependem de T vão, na verdade, usar Object, o mesmo acontece com Bar, que possui nenhum método a mais que Foo. Diferente do que acontece com classes, não são criados métodos sintéticos com a assinatura especializada, por exemplo, em Bla implements Comparable<Bla>, vamos ter um método sintético int compareTo(Bla b).
Qual a relação de erasure e métodos sintéticos com EJB3? Simples, em um Stateless Session Bean definimos as interfaces remota e local, mas não precisamos implementá-las. Isso cria uma espécie de ilusão de ótica, pois de um lado achamos que os métodos implementados possuem a assinatura correta e de outro, por conta do erasure, a assinatura é toda reduzida para Object. O resultado é um só, o container não localiza o método com a assinatura solicitada. O seguinte exemplo demonstra este problema:
public interface Foo
T concat(T t1, T t2);
}
public interface Bla extends Foo
}
@Stateless
@Remote(Bla.class)
@Local(Bla.class)
public class BlaBean {
public String concat(String str1, String str2) {
return str1 + str2;
}
}
Podem testar este código, vão notar que o container reclama não consegue chamar Object concat(Object, Object). Nós queremos implementar o código utilizando o tipo especializado, só que para resolver isso vamos ter que jogar pelas regras do compilador e fazer BlaBean implementar Bla. Dessa forma, implementando a interface, o compilador vai aplicar erasure no bean também e a chamada do método vai funcionar corretamente.
Esta situação me ocorreu quando refatorei a interface de vários SLSB para usarem uma inteface base genérica. Quebrei a cabeça por um bom tempo até me ocorrer que o erasure do generics criou este problema, gostaria de ter encontrado uma solução melhor, mas a que apresentei aqui é um comprometimento razoavel. Meu gosto seria por uma solução da plataforma, como generics implementado direito, ou então uma atualização no RMI para levar reification em conta.
7 responses so far ↓
1 seufagner // Apr 30, 2007 at 5:17 pm
Com uma solução da plataforma, como eu faria para estender meu modelo feito em EJB2.x com EJB3? Visto que o EJB3 da suporte para isso (e eles se preocuparam bastante). Muitos legados não são projetos como o de simples portal, vide Bradesco.
2 kumpera // Apr 30, 2007 at 8:39 pm
Fagner, sinceramente, se você está usando EJB 2.x e não pode migrar, o melhor a fazer é esconder toda dor de cabeça com Spring e xdocket. Não existe muita opção se você está nessa plataforma legado. Mesmo com a spec se preocupando muito com compatibilidade, é um fardo enorme.
3 Paulo Silveira // May 6, 2007 at 2:05 am
O container que voce esta usando é quem nao esta sendo esperto o suficiente. Ele podia, por reflection, perceber que o método da interface mae é parametrizado e inferir T=String da interface Bla, não é mesmo?
Apesar da spec não dizer nada sobre os métodos bridge, acho que o container deveria ter esse cuidado.
4 kumpera // May 6, 2007 at 12:00 pm
Paulo, o problema não é o container, ele não tem como resolver isso. Como um Session Bean não precisa implementar suas interfaces local/remota e não existem os métodos bridge, já que as interfaces que são genéricas. A resolução segundo a spec dita que a mesma assinuatura usada na interface deve estar presente no tipo concreto.
.
Porém o container poderia considerar o fato da interface ser genérica e aplicar o tipo paramétrico na hora de resolver a assinatura, isso resolveria o problema, mas exigiria alteração no protocolo do RMI .
5 Paulo Silveira // May 6, 2007 at 11:18 pm
Kumpera, entendi a quebra do RMI!
Mas e quanto a requisicao ejb? Porque apesar da requisicao ser via RMI, cada container passar as informacoes do metodo a ser invocado da maneira que ele quise, isso nao é padrao. O cara que faz o unmarshal da Invocation do jboss no lado do servidor poderia fazer esse lookup de metodos de acordo com o real tipo parametrizado… nao?
6 kumpera // May 7, 2007 at 9:40 am
Paulo,
O container pode fazer como quiser, mas precisa seguir uma especificação se quiser ser chamado de EJB3. Pelo que lembro, o mecanismo de binding tem que ser funcionalmente igual ao do RMI, ou seja, a assinatura do método do Bean deve ser idêntica àquela utilizada pelo cliente. RMI não possui qualquer provisão de suporte a generics ou erasure.
7 Paulo Silveira // Jun 16, 2010 at 3:47 pm
So pra lembrar que passei por essa dificuldade, 3 anos depois do Kumpera ter escrito aqui :).
Leave a Comment