Gostaria de utilizar o melhor dos dois mundos?
Não é porque o JSF está naturalmente integrado com outras especificações Java EE, que é proibido utilizar ele com Spring.
Por que não utilizar o melhor do JSF com o melhor do Spring? Assim podemos ter a produtividade do JSF junto com todos os recursos do ecossistema Spring.
Um não exclui o outro. Pelo contrário, eles são perfeitamente integráveis e veremos agora como fazer isso.
Entre outras coisas, nesse artigo, você vai aprender:
- Por que integrar JSF e Spring
- Quais os benefícios dessa integração
- Como integrar JSF e Spring para dar aos beans do Spring Framework o papel de um Managed Bean
- A adicionar o ViewScope no Spring
Interessado? Vamos lá então.
Por que integrar JSF e Spring?
Uma pequena desvantagem nessa integração é o fato do Spring ainda não possuir, nativamente, o escopo de view.
Só que, como o Spring nos dá a possibilidade de criar escopos customizados, esse é um problema que podemos resolver sem gambiarras e até com elegância, eu diria.
Sobre a outra configuração que precisa ser feita no faces-config.xml (ainda falaremos dela nesse artigo), não acho que isso deva ser uma desvantagem de peso na decisão de se utilizar JSF e Spring.
Muitos preferem JSF com CDI, pois eles se integram naturalmente, mas se olharmos para o todo, as coisas não ficam muito mais simples do que utilizar JSF com Spring.
Pense comigo, quem usa o JSF com CDI:
- Está utilizando um servidor de aplicações que, apesar dos seus benefícios, carregam suas complexidades; ou
- Tem que adicionar configurações extras para fazê-lo funcionar em um container de servlet qualquer (Tomcat, por exemplo).
Ou seja, o ambiente como um todo não fica mais ou menos complexo porque você utilizou Spring ou CDI.
O que mais deve pesar na decisão de usar JSF com Spring é o que você pensa sobre o futuro:
- Suas próximas aplicações serão baseadas em Spring?
- Você pretende migrar as aplicações existentes para Spring?
Existe outro ponto importante (e que influencia nas respostas para as perguntas acima), que é o seu domínio (ou o domínio do seu time) sobre a tecnologia.
O que não pode ter o maior peso é querer usar JSF com CDI só porque eles fazem parte de uma especificação, digamos, “formal” do Java EE. Isso pode ser levado em conta, claro, mas não acho que deve ser a característica que vai decidir.
Quais os benefícios de se integrar JSF e Spring
Não preciso dizer muito sobre os benefícios aqui.
Em um artigo anterior, o Normandes falou especificamente sobre Spring e as qualidades do mesmo.
Ele deu uma definição bem bacana para o framework que já embute também as vantagens:
“O Spring é composto por diversos projetos que ajudam os desenvolvedores a criarem aplicações Java mais simples, rápidas e flexíveis!”
Falando de benefícios ligados diretamente aos Managed Beans do JSF, o Spring vai adicionar características importantes, como:
- Injeção de dependências flexível;
- Profiles (produção, desenvolvimento, testes e qualquer outro que quiser);
- Gerenciamento de transações;
- Suporte a AOP;
- Entre outras coisas.
…que são interessantes para um desenvolvimento organizado e com produtividade.
Claro que temos funcionalidades semelhantes entre JSF e CDI também, mas nesse artigo aqui estou focando em Spring.
Entendendo a classe ELResolver
Para que você entenda como a integração funciona, vamos olhar o recurso do JSF que o Spring se utilizou para fazê-la.
Acredito que assim você terá um domínio maior sobre a funcionalidade.
Todas as vezes que o JSF encontra uma expression language como:
#{clienteBean.cliente.nome}
…ele delega a resolução dela para alguma classe que estenda a classe abstrata ELResolver
(da API de Expression Language).
Interessante notar que não existe somente uma implementação de ELResolver
, são várias e cada uma com seu objetivo.
Temos implementações que ajudam a resolver expressões que usam mapas, listas, propriedades com escopos específicos (como o Flash Scope), entre outros.
Dentro do JSF (Mojarra, especificamente) temos exemplos desse tipo de classe:
- ManagedBeanELResolver;
- ResourceELResolver;
- ScopedAttributeELResolver;
Veja mais:
São essas classes as responsáveis por fazer com que cada parte da EL seja processada.
Para ficar mais claro como uma EL é resolvida, vamos olhar dentro da classe ELResolver
. O método que vamos analisar é o getValue
.
Veja:
public abstract class ELResolver { public abstract Object getValue(ELContext context, Object base, Object property); ... }
A primeira coisa que o JSF tem que fazer é resolver a raiz de uma EL. Raiz essa que, na grande maioria das vezes, é um Managed Bean.
Tomando como exemplo a EL que mostrei acima, primeiro teria que ser resolvido o valor de clienteBean
.
Para resolver a raiz da nossa EL, o método getValue
(de uma classe concreta qualquer que estenda ELResolver
) receberia um contexto de EL (do tipo ELContext
), o parâmetro base
como nulo (afinal, agora estamos resolvendo a base) e o parâmetro property
como clienteBean
(em string).
O que a classe deve fazer é retornar um objeto que seja referente a string “clienteBean”. Nesse exemplo seria uma instância da classe ClienteBean
.
Resolvido o valor de clienteBean
, agora o método getValue
(da mesma classe concreta ou de outra) vai receber o mesmo contexto EL, o parâmetro base
com o valor referente ao que foi encontrado para clienteBean
e o parâmetro property
com o valor igual a cliente
.
Esse ciclo vai continuar até que a propriedade da ponta (nome
, no caso) seja resolvida.
Voltando aqui sobre a integração entre JSF com Spring, no nosso caso de querer Managed Beans instanciados pelo Spring, basta criar uma implementação de ELResolver
que busque a propriedade clienteBean
dentro do application context do Spring.
Para nossa conveniência, o Spring já nos dá essa implementação.
Ele tem uma classe que se chama SpringBeanFacesELResolver
, que estende de SpringBeanELResolver
, e essa sim, estende ELResolver
.
public class SpringBeanELResolver extends ELResolver { @Override public Object getValue(ELContext elContext, Object base, Object property) throws ELException { if (base == null) { // o Spring só quer resolver a base, ou seja, a primeira propriedade String beanName = property.toString(); BeanFactory bf = getBeanFactory(elContext); // fábrica de beans Spring. if (bf.containsBean(beanName)) { if (logger.isTraceEnabled()) { logger.trace("Successfully resolved variable '" + beanName + "' in Spring BeanFactory"); } elContext.setPropertyResolved(true); // avisa ao JSF que o bean Spring existe e, portanto, a propriedade foi resolvida. return bf.getBean(beanName); } } return null; // já que a propriedade NÃO foi resolvida, o mais óbvio é retornar nulo } ... }
O que precisamos entender agora é como avisar o JSF que temos uma implementação de ELResolver
que vai tornar os nossos beans Spring acessíveis via EL.
Integrando Spring com JSF
Quando temos um ELResolver
, seja ele qual for, informamos ao JSF da existência do mesmo através da tag el-resolver
dentro do faces-config.xml:
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application> </faces-config>
No exemplo acima, configuramos justamente a implementação do Spring para a classe ELResolver
, que será responsável por retornar os beans Spring para o JSF.
Agora, ao invés de usar Managed Beans JSF, você vai usar beans Spring.
Nossa classe ClienteBean
, que era um Managed Bean JSF:
@ManagedBean @RequestScope public class ClienteBean { ... }
…vai passar a ser um bean Spring:
@Component // substitui @ManagedBean @Scope("request") // substitui @RequestScope public class ClienteBean { ... }
Dessa forma, temos um bean Spring fazendo o papel de Managed Bean do JSF.
De resto, você vai continuar usando o Spring e JSF normalmente. O uso nas páginas JSF continua o mesmo. As EL’s não precisam ser alteradas.
Adicionando o suporte ao ViewScope nos beans Spring
Um dos escopos mais usados no JSF 2.x é o de view. Esse escopo não existe no Spring, mas podemos criá-lo facilmente.
No Spring temos um recurso que nos permite criar escopos customizados para ele. Vamos utilizar esse recurso para adicionar o escopo de view no Spring
O legal é que nem precisamos nos preocupar com o ciclo de vida dos beans, pois, vamos utilizar o viewMap
do próprio JSF, que já faz esse gerenciamento.
Observe como fica simples o nosso escopo:
public class ViewScope implements org.springframework.beans.factory.config.Scope { public Object get(String name, ObjectFactory<?> objectFactory) { if (FacesContext.getCurrentInstance().getViewRoot() != null) { Map<String, Object> viewMap = FacesContext.getCurrentInstance() .getViewRoot() .getViewMap(); // Map do JSF cujo os valores são referentes ao escopo de view if (viewMap.containsKey(name)) { return viewMap.get(name); } else { Object object = objectFactory.getObject(); // da próxima vez que for requisitado, dentro do escopo de view, // ele não precisará ser construído. viewMap.put(name, object); return object; } } else { return null; } } public Object remove(String name) { if (FacesContext.getCurrentInstance().getViewRoot() != null) { // simplesmente, remove o bean Spring do ViewMap do JSF return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name); } else { return null; } } ... }
O que é necessário agora é avisar ao Spring que criamos esse escopo.
Para o registro, basta invocarmos o método registerScope
da interface ConfigurableBeanFactory
:
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry { void registerScope(String scopeName, Scope scope); ... }
Existem várias formas de chegar a esse método, mas vamos utilizar uma disponibilizada pelo Spring, que é através da classe CustomScopeConfigurer
.
Se você usa Spring com XML, então pode fazer assim:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="view"> <bean class="com.algaworks.jsfspring.ViewScope"/> </entry> </map> </property> </bean>
Caso use a configuração via anotações, então pode usar:
@Configuration public class ViewScopeConfig { @Bean public static CustomScopeConfigurer customScopeConfigurer() { Map<String, Object> scopes = new HashMap<>(); scopes.put("view", new ViewScope()); CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer(); customScopeConfigurer.setScopes(scopes); return customScopeConfigurer; } }
Repare que o método acima é estático. Isso foi por uma recomendação do próprio framework, pois a classe CustomScopeConfigurer
é um BeanFactoryPostProcessor
.
É melhor que seja assim para evitar problemas com o processamento de alguma anotação como, por exemplo @Autowired
, que esteja dentro de ViewScopeConfig
. Em nosso caso, não tem, mas como é recomendado pelo framework, então eu utilizei o modificador static
.
Agora, nosso ClienteBean
pode ser configurado assim:
@Component @Scope("view") // Agora podemos usar o escopo de view public class ClienteBean { ... }
E quando o JSF tentar resolver a base da EL:
#{clienteBean.cliente.nome}
…ele vai pedir nosso ClienteBean
para a classe SpringBeanFacesELResolver
que, através de BeanFactory
, vai encontrá-la em nossa classe ViewScope
.
Assim temos o JSF integrado ao Spring com suporte para o escopo de view.
Pequeno aviso sobre usar Spring com JSF
Quando for integrar as duas tecnologias, como em nosso código de exemplo, pode ser que você veja uma mensagem como:
out 14, 2016 11:29:42 AM com.sun.faces.application.view.ViewScopeManagerINFO: CDI @ViewScoped manager unavailable java.lang.NoClassDefFoundError: javax/enterprise/context/spi/Contextual at com.sun.faces.application.view.ViewScopeManager. (Unknown Source) at com.sun.faces.application.view.ViewScopeManager.getInstance(Unknown Source) at com.sun.faces.application.view.ViewScopeEventListener.processEvent(Unknown Source) at javax.faces.event.SystemEvent.processListener(Unknown Source) at javax.faces.event.ComponentSystemEvent.processListener(Unknown Source) at com.sun.faces.application.ApplicationImpl.processListeners(Unknown Source) at com.sun.faces.application.ApplicationImpl.invokeListenersFor(Unknown Source) at com.sun.faces.application.ApplicationImpl.publishEvent(Unknown Source) at javax.faces.component.UIViewRoot.getViewMap(Unknown Source) at javax.faces.component.UIViewRoot.getViewMap(Unknown Source)
A primeira vista parece ser algum problema com a integração, mas é, simplesmente, uma INFO dizendo que não foi encontrado o gerenciador de @ViewScope
do CDI.
Fique tranquilo, pois, não terá impactos na sua aplicação.
A versão do JSF que utilizamos no exemplo do artigo foi a 2.2.9:
<dependency> <groupId>org.glassfish</groupId> <artifactId>javax.faces</artifactId> <version>2.2.9</version> </dependency>
Conclusão
Aprofundamos no entendimento sobre a classe abstrata ELResolver
para mostrar qual a forma utilizada pelo Spring para integração das duas tecnologias.
Mostrei também como é feita a configuração da classe SpringBeanFacesELResolver
dentro do faces-config.xml.
Por último, configuramos o escopo de view dentro do Spring para termos suporte equivalente ao da anotação @ViewScope
do JSF.
No caso de querer se aprofundar no assunto de JSF, tenho uma boa recomendação pra você!
Para aprender mais sobre JSF, você pode baixar agora o e-book que o Thiago escreveu.
Depois do download, vai ser bacana se deixar seu comentário dizendo se gostou, se não gostou, uma opinião, crítica ou elogio.
Um abraço pra você e até uma próxima!
PS: Você pode acessar o código-fonte desse artigo com os exemplos no GitHub: http://github.com/algaworks/artigo-integracao-jsf-spring
Olá,
o que você achou deste conteúdo? Conte nos comentários.