Aproveitando os beans do Spring em suas páginas JSF

Esses dias estava tendo uma conversa bem produtiva com um amigo de trabalho, Tarso Bessa, o cara é muito safo em desenvolvimento e sempre me aparece com umas idéias bem bacanas. Nessa conversa ele chegou com uma idéia bem interessante sobre aproveitarmos a integração do Spring com JSF, a idéia se resume em aproveitar o variable resolver do Spring nas páginas JSF, assim poderíamos acessar qualquer bean gerenciado pelo Spring (Service, DAO, Repository, whatever) nas páginas através de JSF EL sem a necessidade de intermediar o acesso à este bean com um managed bean do JSF.

O problema

O problema realmente surgiu enquanto o Tarso implementava um mecanismo de CRUD genérico para nossa aplicação, foi aí que começamos a discutir sobre o problema e em seguida a solução.

Na verdade, no projeto que estamos desenvolvendo nós utilizamos muitas combo-boxes (tr:SelectOneChoice pois utilizamos o Myfaces Trinidad) nas nossas páginas e ter que criar um ou mais métodos nos nossos managed beans somente para popular essas combos com dados trazidos diretamente do banco de dados -sem qualquer lógica-, além de ser uma tafera repetitiva, não era nada cômodo.

No final das contas só queríamos não escrever mais código repetitivo nos nossos managed beans (principalmente os que estendiam o nosso CRUD genérico) simplemente para podermos acessar nas páginas uma lista de objetos trazidos do banco de dados.

A solução

Com a integração do Spring com JSF é possível fazer injeção de dependências (DI) dos beans do Spring dentro dos managed beans da aplicação pelo faces-config.xml, algo como:

[faces-config.xml]

<managed-bean>
    <managed-bean-name>bacalhauBean</managed-bean-name>
    <managed-bean-class>bean.faces.BacalhauBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>someService</property-name>
        <property-class>model.service.SomeService</property-class>
        <value>#{someService}</value>
    </managed-property>
</managed-bean>

Imaginem que nós tenhamos um bean chamado “someService” configurado no spring-config.xml, ok?
Agora observem que o bean gerenciado pelo Spring é resolvido através da EL do JSF, #{someService}, então por que não utilizar-se disso para acessarmos diretamente este bean dentro das páginas? 😀
Será isso que iremos fazer logo abaixo!

Imagine que nossa implementação da interface SomeService seja algo do tipo:

[SomeServiceImpl.java]

public class SomeServiceImpl implements SomerService {

    // outros métodos deste serviço

    public List<Foo> findAll() {
        // executa a pesquisa através de um DAO, ou repositório, whatever..
    }

    public List<Foo> getAll() {
        // faz chamada ao método findAll
        return this.findAll();
    }
}

Assim no código da página nós precisaríamos fazer apenas isto:

[foo.jsp]

<h:selectOneMenu id="fooId" value="#{bacalhauBean.fooId}">
    <t:selectItems value="#{someService.all}" var="o" itemValue="o.id" itemLabel="o.name" />
</h:selectOneMenu>

Observem que estou passando a lista de objetos para o componente através da EL #{someService.all} que reflete o método SomeServiceImpl.getAll() do bean gerenciado pelo Spring, atentem também que estou utilizando o componente t:selectItems do Myfaces Tomahawk para que eu possa iterar uma lista de objetos comuns, pois sem ele seria necessário passar uma lista de SelectItem’s, o que não ficaria legal nas nossas classes de serviços.

O melhor de tudo é que não há a necessidade de utilizar um managed bean para intermediar a lista de dados para o componente, o EL do JSF acha o bean “someService” no contexto do Spring e faz a chamada ao método para você (é necessário que o método comece com “get” para que a EL consiga executar corretamente, por isso criamos um método chamado getAll() na classe do SomeServiceImpl.java).

Graças ao variable resolver do Spring você poderá utilizar o #{someService.all} em qualquer componente que espere uma lista de objetos, como um h:dataTable, h:selectManyMenu ou qualquer outro. Além do mais você poderá obter um objeto qualquer como um entity, ou um mapa, ou uma lista ordenada e/ou filtrada diretamente dos beans gerenciados pelo Spring e utilizá-los nas tuas páginas sem dificuldades.

Uma coincidência é que enquanto o Tarso estava fazendo os testes e implementando a solução ele caiu naquele velho probleminha do selectOneMenu, não exatamente como no cenário que eu havia blogado, mas o problema e a solução foram a mesma, e acabamos resolvendo este problema rapidamente sem perder tempo. É como eu disse no final daquele post, “[…] quem não passou provavelmente irá passar algum dia.” :)

O mais engraçado..

O mais engraçado disso tudo é que já temos tudo isso pronto e muito melhor implementado com o JBoss Seam, o Seam estendeu a EL do JSF e com isso conseguimos acessar qualquer componente no seu contexto através de EL, como DAOs, repositorios, serviços etc, e o melhor é que a sua EL estendida nos fornece a possibilidade de executar métodos parametrizados, ou seja, com ele podemos ter algo como isto:

<h:commandButton action="#{hotelBooking.bookHotel(hotel, user)}" value="Book Hotel"/>

Simplesmente fantástico!
Enfim, para quem não tem o JBoss Seam no projeto, pode ter um pouco do gostinho dele com o Spring.

Concluindo..

Como achei o problema e a solução interessantes eu quis compartilhar com os pouquíssimos leitores deste blog, fica aí a dica senhores.
Valeu Tarso Bessa, valeu gente.

25 thoughts on “Aproveitando os beans do Spring em suas páginas JSF

  1. Agregando a isso podemos re-haver o conceito de OO onde tenhamos objetos de domínio com suas devidas atividades.

    Ou seja, em vez de injetar um Service na camada View, vc deixa chegar até ela somente o objeto de domínio que tem tudo que é necessário para ela mesma, assim vc não sabe que existem services etc, e sim funcionalidades

    [s]
    Baiano

  2. Rafael, valeu pela lembrança. Gostei do post.

    Complementando, as coisas poderiam ficar até um pouco mais radicais se quiséssemos configurar os managed beans também no Spring.

    O chato seria perder o suporte da IDE, mas em compensação muitas facilidades vindas do Spring seriam mais facilmente aproveitadas.

    Voltando ao Seam. De cara, ele pode causar uma impressão de que a visão conhece demais o modelo, mas na verdade um texto como #{hotelBooking.bookHotel(hotel, user)} pode ser muita coisa, inclusive a implementação disso mudar no futuro e a visão nem sabe. Muito bom.

    Como havia dito para você, eu tinha construído um herança do h:commandButton (em 2004) que realizava uma chamada de um método com parâmetros. Foi bom como experiência, mas agora que o Seam está trazendo essas facilidades, a gente usa.

    Como o Milfont falou, está explodindo de soluções. 😀

    []’s

  3. @Tarso
    O chato seria perder o suporte da IDE, mas em compensação muitas facilidades vindas do Spring seriam mais facilmente aproveitadas.

    Pois é, no link do blog do Cagatay ele comenta isso, http://cagataycivici.wordpress.com/2007/09/10/using-spring-to-manage-jsf-beans/
    Contudo ainda não acho uma boa deixar os managed bean sobre responsabilidade do Spring, sei lá.. acho que cada um no seu lugar. Mas os CRUDs vejo problemas.

    Acredito também que se for para deixar tudo por conta do Spring é melhor utilizar logo o Seam e ser feliz.

    Eita, você utilizou Spring em 2004? Oloco!
    Valeu Tarso!

  4. Excelente post!

    Sobre o Seam, andei vendo a sua arquitetura e como o Tarso mencionou, ele deixa as camadas acopladas. Pra você ter idéia o backing bean/managed bean é um EJB. :)

    Espero que em outras versões do Seam a turminha melhore nesse aspecto. Pensando bem, será que a idéia deles não foi essa? 😀

  5. Os exemplos básicos do SEAM ele realmente “mistura” as layers, mas isso não quer dizer que ele trabalhe apenas assim. É só para demonstrar o quanto simples ele é e o pouco código que é necessário para escrever uma funcionalidade.

    Enfim, há várias maneiras de se desenvolver com SEAM, os exemplos são apenas uma delas, e isso realmente não parece legal. Para você ter idéia é possível gerar CRUDs a partir da entidades JPA, e o melhor, pode-se criar todo um CRUD utilizando apenas algumas configurações XML, ou seja, nada de managed bean.

    O ideal é estudar o SEAM, ler a sua documentação e ter noção do que é possível se fazer com ele.

    Valeu Carneiro, abraços.

  6. Cara eu já fazia isso deixando todas as listas que carregam selectOneMenu em um managed-bean separado, na página fica igual mas no faces-config.xml declaro essa classe com as listas como se fosse um managed-bean normal. Tem alguma diferença de fazer isso com DI do spring?

  7. Olá Fabio,

    Não, não há diferenças entras elas, o objetivo é o mesmo, contudo a idéia de aproveitar os beans do Spring é a não necessidade de escrever métodos de leitura (get’s) nos teus managed beans somente para popular as combos ou qualquer outro componente. No seu caso o problema é que uma hora esse teu managed bean vai inchar, com muitos métodos de leitura e beans do Spring injetados nele, coisa que você pode evitar acessando os beans do Spring diretamente, ou seja, você escreve menos código e ainda reaproveita o código dos beans do Spring.

  8. Muito massa mesmo esse teu post cara. O que me chama a atenção é o fato de se quebrar (ou aparentemente quebrar) as camadas de controle nessa história. A visão vai direto ao DAO que lhe trás resultados. Mas a praticidade e realmente o fato de não ter que se escrever os get’s só para fazer combos, é no mínimo interessante.

    Vlw ai a dica e o post. Parabéns, abraços.

  9. E ae Paulo, cara, não há problemas da tua camada de visão (jsp, xhtml etc) acessar teus objetos de domain model (entities, services, repositorios, whatever), eles existem para serem acessados mesmo, a visão está acessando somente *dados* e não executando regras :) Caso seja preocupante que a visão acesse teu domain model então você pode criar interfaces para restringir o acesso à alguns métodos.

    Acredite, sua visão acessar objetos de domain model somente para recuperar dados e exibi-los não vai quebrar o encapsulamento das tuas layers :)

  10. segundo o padrão MVC original (não o model 2), a visão observa as mudanças do modelo para alterar a renderização.
    Acessar diretamente o modelo então não está errado nesse ponto. O model 2 erroneamente linearizou esse acesso por uma variação do modelo da NeXT.

  11. Rafael, também costumo acessar alguns métodos de leitura do meu repositorio ou service através de EL. Tive um problema ao interar um retorno de um desses métodos com o foreach. Pois o foreach chama o método n-vezes para o seu devido controle, e dessa forma tava gerando diversas consultas desnecessárias. No selectItems ou no dataTable funciona da mesma forma, ou você usa cache de query ?

    Abraço

  12. Bem, se eu entendi bem o teu problema você está fazendo algo do tipo, certo:


    < c:forEach items="#{beanDoSpring.todosRegistros}" var="o">

    Neste caso provavelmente a tag deve chamar o método geTodosRegistros() a cada iteração, o que é um problema. O que você poderia fazer seria jogar o valor de retorno em algum objeto no escopo de página antes de iterar, algo como:


    < c:set var="lista" value="#{beanDoSpring.todosRegistros}"/>
    < c:forEach items="${lista}" var="o">

    Bem, a “mágica” está na tag c:set. Acredito que assim deva funcionar.
    Abraços e boa sorte ae.

  13. Rafael, eu consegui utilizar esse esquema de executar métodos parametrizados com JSF + Spring criando novos Application e Application Factory que decoram os originais para utilizar o Expression Factory do JBoss EL.

  14. Isso mesmo. Criei essas classes para usar a EL do JBoss. Ai no faces-config.xml é só declarar um novo ApplicationFactory. Só um detalhe: isso só funciona com Facelets.

  15. Wow! Bacana David, bem que você poderia disponibilizar essas classes em algum lugar, isso é muito útil, eu fiquei bem interessado! Contudo, qual o motivo de não funcionar com o Facelets?

    Abraços!

  16. Claro, posso sim. Eu vou colocar lá no JEEBrasil assim que eu realizar mais testes e vir que está tudo ok. E, na verdade, ele só funciona se for com Facelets. É uma limitação do próprio JBoss EL, pelo que entendi.

  17. Cara… gostei muito, parabéns pelo artigo.
    Gostaria de tirar uma dúvida que já vem me incomodando a muito tempo, tem alguma coisa a ver com SelectOneMenu.
    Quando entrei na empresa atual a galera utilizava VO, e quando tinhamos que fazer combobox(SelectOneMenu) colocavamos o id + descrição do valor a ser exibido dentro deste VO. Depois de insistir bastante consegui autorização da galera para fazer um CRUD sem VO, utilizando diretamente as Entidades(JPA).
    O problema que encontrei durante o trabalho foi com os SelectOneMenu, pois quando a pessoa seleciona um item ele nunca retorna o objeto(Município) e sim o seu ID.
    Após segundos pesquisando no google encontrei o Converter que resolvia este problema, mas me pareceu um pouco complicado utiliza-lo pois após a seleção do item era necessário ir novamente ao banco fazendo a pesquisa pelo ID do municipio, essa solução me pareceu ser “pernada” pois se já tenho esse objeto em uma lista porque preciso busca-lo novamente no banco de dados? Sabe se tem outra maneira de resolver este problema? Minha idéia era criar um componente que retornasse o meu objeto ao invez do id, mas preferi perguntar ao google por soluções já prontas.

  18. Olá Douglas,

    Em primeiro lugar gostaria de parabeniza-lo pela sua iniciativa, pois o uso inapropriado de VOs é danoso e caro na manutenção, convença seus amigos da equipe a deixarem VOs de lado.

    Eu sempre aconselho desenvolvedores a utilizarem o objeto (entidade) no lugar do Id como value do SelectItem, fica muito melhor de se trabalhar.

    Eu acho que sei de qual converter você está falando, e também não concordo muito com a finalidade dele para a maioria dos casos, contudo em alguns ele se encaixa perfeitamente.

    Existe algumas soluções para o que você quer, eu fiquei de postar sobre o assunto, mas já posso te adiantar que uma maneira simples seria se utilizar do mapa de atributos do teu componente(h:selectOneMenu), assim no método getAsString() do teu converter você jogaria os objetos como atributo do componente e no método getAsObject() você obteria os objetos através das chaves. Outra maneira seria você obter os nós filhos (SelectItems) do componente h:selectOneMenu no método getAsObject(), é mais complicado, mas funciona bem.

    Em breve acredito que estarei postando sobre isso.
    Abraços e boa sorte.

Leave a Reply to Handerson Frota Cancel reply

Your email address will not be published. Required fields are marked *