Outro dia, na Caelum, passamos por uma situação interessante. Dois colegas de trabalho, Lucas e Marcio, estavam programando no projeto de final de curso deles e tinha a seguinte situação: o projeto é todo baseado no Spring, mas mesmo assim eles decidiram realizar o deploy em um servidor de aplicação, no caso o Wildfly. É uma decisão interessante, ao invés de optar por um caminho ou outro, eles juntaram as forças das duas plataformas. Vai ser o tema do próximo post deste blog :).
E a decisão valeu a pena! Eles precisavam lidar com WebSockets e a especificação no JAVA EE 7 deixa muito simples implementar qualquer funcionalidade baseada nesse protocolo. O problema é que agora eles tem uma classe que é gerenciada pelo servidor de aplicação, mas precisam acessar objetos que estão gerenciados pelo Spring. O que fazer? Os container não se falam e, nessa situação, só nos restou partir para um workaround(gambi). Dê uma olhada no esboço da classe deles:
@ServerEndpoint("/channel/sales") public class AgileBoardEndpoint { @Inject private ConnectedUsers connectedUsers; //dao gerenciado pelo Spring @AutoWired private TaskDao tasks; @OnOpen public void onNewUser(Session session){ connectedUsers.add(session); } @OnMessage public void onMessage(String message) { tasks.save(new Task(message)); connectedUser.alert(task); } }
Não adianta eles anotarem nenhum atributo com AutoWired, já que, como dissemos, o objeto desta classe não vai ser instanciado pelo Spring. Para resolver tal situação é necessário que você acesse o objeto que representa uma parte do container do Spring de maneira programática.
@ServerEndpoint("/channel/sales") public class AgileBoardEndpoint { @Inject private ConnectedUsers connectedUsers; @OnOpen public void onNewUser(Session session){ connectedUsers.add(session); } @OnMessage public void onMessage(String message) { ApplicationContext ctx = ApplicationContextHolder.getInstance(); TaskDao tasks = ctx.getBean(TaskDao.class); tasks.save(new Task(message)); connectedUser.alert(task); } }
A classe ApplicationContext possui o método getBean, que te permite fazer o lookup de um objeto pela sua classe. A instância retornada foi criada exatamente da mesma forma que seria se você tivesse pedido a injeção pelo atributo usando o AutoWired. A pergunta que fica é: como vamos obter uma instância do objeto do tipo ApplicationContext? É justamente que entra nossa solução de contorno(workaround).
@Component public class ApplicationContextHolder implements ApplicationContextAware{ private static ApplicationContext instance; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.instance = applicationContext; } public static ApplicationContext getInstance(){ return instance; } }
Implementamos a interface ApplicationContextAware quando precisamos ser notificados da criação do ApplicationContext. É parecido com um desses listeners que implementamos quando queremos ser notificados, por exemplo, sobre o inicio do nosso servlet container. Como, em geral, todas as classes ficam gerenciadas sob o mesmo contexto, podemos guardar a instância passada em um atributo estático.
O benefício dessa solução é que conseguimos acessar o contexto do Spring de qualquer lugar, foi justamente o que fizemos a partir da classe que lidava com os WebSockets do JAVA EE. É um bom truque para integrar duas pontas que não se falam muito. O malefício, é exatamente o mesmo :). Você pode acessar de qualquer lugar, tem que tomar cuidado para não sair usando este poder onde não é necessário e acabar prejudicando a manutenção do código. Outro ponto ruim é que você acabou de deixar seu código mais difícil de testar.
Este tipo de decisão acontece todos os dias enquanto estamos desenvolvendo. O importante é saber o que ganhamos e perdemos, porque assim vamos por um caminho baseado em um julgamento crítico, sabendo exatamente as consequências. No mundo dos sonhos, o Spring vai implementar a especificação do CDI e vai ficar tudo bem para a gente. Lembre, saber tirar proveito da tecnologia que você usa, faz muita diferença no produto final que será entregue.
[…] conseguir o ApplicationContext, eu usei a mesma tática já citada aqui no blog. Por sinal, eu só fiz isso porque não consegui achar um jeito do Spring gerenciar o […]
LikeLike