Spring & JSF: Integrando
Dado a crescente necessidade de se adequar ao cenário multidisciplinar, uma das tarefas que eu mais relutei em começar foi o aprendizado de um framework de camada de visão.
Mas com tantas opções no mercado, qual eu deveria escolher para iniciar? AngularJS? JSF? Struts? Alguma outra coisa?
Bom, dado uma outra resistência que sempre tive, linguagens não tipadas, excluí a princípio o AngularJS, apesar de saber que isso é uma decisão temporária. No contexto atual o JavaScript tomou uma proporção inimaginável há alguns anos atrás. Lembro-me do meu primeiro emprego em que Flex seria a implementação do futuro para aplicações RIA =)…
Hoje o JavaScript é uma realidade (já era há mais tempo), querer ignorá-lo é no mínimo um tiro no pé.
Struts? Bom… Faz muito tempo que trabalhei com Struts, mas o que vejo hoje não são as pessoas convergindo para ele.
Foi então que me lembrei de um curso que fiz na Caelum: FJ26 — JavaWeb com JSF e CDI
À época, o curso era disponibilizado com JARs a serem adicionados no projeto, bem como classes de DAO já implementadas. O foco do curso não estava na configuração do JPA e Hibernate, e sim em JSF e CDI. Não sei como está o curso agora, visto que a Caelum constantemente evolui seus cursos.
Uma vez que achei um curso de exemplo para começar, a grande questão seria como poderia “refatorar” o curso para aprender outras tecnologias que eu tinha interesse nesse ano.
A arquitetura que optei foi:
Spring-Boot é algo que ainda vou escrever mais algum dia, mas por hora, ainda estou tateando suas nuâncias. A configuração de um projeto dado um projeto pai já configurado, que evitam conflitos entre bibliotecas foi algo que achei muito interessante no projeto.
Sobre o Spring-Data também irei fazer outro post. Para aqueles que já viram algumas APIs ou classes genéricas que te ajudam na persistência de dados, e estão acostumados com classes abstratas utilizando Generics, o Spring-Data utiliza uma ideia mais interessante: interfaces, mas isso é assunto para outro post.
Vamos ao JSF e Spring
Utilizei o seguinte tutorial como base para montagem da minha arquitetura. Abaixo no post é possível encontrar o repositório do código (algo que demorei muito para achar quando lia o post =P)
Como o foco do curso da Caelum era a criação de um projeto JSF sem Spring, quando da integração deste, uma questão ficou no ar: Quem vai gerenciar meus beans? O JSF ou o Spring?
Este post do StackOverflow me ajudou muito.
Para início explicando que a seguinte estrutura é inválida:
@ManagedBean vs @Controller
First of all, you should choose one framework to manage your beans. You should choose either JSF or Spring (or CDI) to manage your beans. Whilst the following works, it is fundamentally wrong:
@ManagedBean // JSF-managed.@Controller // Spring-managed.public class BadBean {}
You end up with two completely separate instances of the very same managed bean class, one managed by JSF and another one managed by Spring. It’s not directly clear which one would actually be used in EL when you reference it as #{someBean}. If you have the SpringBeanFacesELResolver registered in faces-config.xml, then it would be the Spring-managed one, not the JSF-managed one. If you don’t have that, then it would be the JSF-managed one.
Além disso, o autor da resposta mencionou algo que só fui me ater depois, mas chegarei lá:
Also, when you declare a JSF managed bean specific scope, such as @RequestScoped, @ViewScoped, @SessionScoped or @ApplicationScoped from javax.faces.* package, it will only be recognized and used by @ManagedBean. It won’t be understood by @Controller as it expects its own @Scope annotation. This defaults to singleton (application scope) when absent.
@ManagedBean // JSF-managed.@ViewScoped // JSF-managed scope.@Controller // Spring-managed (without own scope, so actually becomes a singleton).public class BadBean {}
When you reference the above bean via #{someBean}, it would return the Spring-managed application scoped bean, not the JSF-managed view scoped bean.
Outra observação relevante muito semelhante ao já abordado acima:
@ManagedProperty vs @Autowired
The JSF-specific @ManagedProperty works only in JSF-managed beans, i.e. when you’re using @ManagedBean. The Spring-specific @Autowired works only in Spring-managed beans, i.e. when you’re using @Controller. Below approaches are less or more equivalent and cannot be mixed:
@ManagedBean // JSF-managed.@RequestScoped // JSF-managed scope.public class GoodBean {@ManagedProperty(“#{springBeanName}”)private SpringBeanClass springBeanName; // Setter required.}@Component // Spring-managed.@Scope(“request”) // Spring-managed scope.public class GoodBean {@Autowiredprivate SpringBeanClass springBeanName; // No setter required.}
Escolhi a gerência via Spring dado já ter mais familiaridade com o mesmo, mas isso me levou a um problema: Escopo
Na apostila da Caelum, em uma determinada parte, quando da explicação da diferença de escopos de um bean foi dito:
O problema é que o escopo Request perde as informações mesmo depois de cada requisição para a mesma View, desta forma, o seu ManagedBean perde o elemento que está guardando o id. Para resolvermos esse problema, devemos passar o nosso ManagedBean para o escopo de view, utilizando a anotação @ViewScoped, assim ele manterá os dados durante as diversas requisições que fazemos para a mesma view.
Após algumas buscas encontrei esse post, que dentre outras coisas mencionava o seguinte comparativo entre escopos JSF e Spring:
Fui então procurar como usuários Spring solucionavam esse problema e achei essa seguinte implementação.
Apenas mais um detalhe me inquietava, sempre gostei da implementação Java-Based do Spring. Tomei por base o seguinte post, e realizei minha configuração da seguinte forma:
package br.com.caelum.invoice.config;import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.config
.CustomScopeConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import br.com.caelum.invoice.spring.scope.ViewScope;@Configuration
public class SpringConfig { @Bean
public CustomScopeConfigurer getScopeConfigurer() { final CustomScopeConfigurer scopeConfigurer =
new CustomScopeConfigurer(); final Map<String, Object> scopes = new HashMap<>();
scopes.put(“view”, this.getViewScope()); scopeConfigurer.setScopes(scopes); return scopeConfigurer;
} public ViewScope getViewScope() {
return new ViewScope();
}
}
Alterei então meu bean para
@Controller
@Scope(“view”)
public class ProductBean implements Serializable {
…
}
De resto, meus amigos do JSF que me perdoem, nem sei se estou fazendo isso certo, mas as implementações em JSF sempre me deram uma agonia por parecerem quebrar a Lei de Demeter. Alterei meus beans para
public class ProductBean implements Serializable { … private Product product = new Product(); public final String getDescription() {
return this.product.getDescription();
} public final Long getId() {
return this.product.getId();
} public final String getName() {
return this.product.getName();
} public final Double getPrice() {
return this.product.getPrice();
}
}
Assim, ao invés de utilizar ${productBean.product.description} utilizo ${productBean.description}
Enfim… esse post foi maior do que eu imaginava… por hoje é só