Aprenda a utilizar o Spring Web Flow
Saiba como implementar fluxos de navegação em uma aplicação Web com um exemplo usando Spring e Hibernate.
Frameworks Model-View-Controller são vastamente utilizados e seus benefícios são amplamente conhecidos. Por exemplo, a forma como o padrão MVC distribui responsabilidades nas camadas da aplicação faz com que tenhamos camadas mais magras e, consequentemente, código mais organizado e manutenção mais simples. No Struts vemos isso ilustrado na medida em que escrevemos código Java em actions, ao invés de manter grandes parte da lógica do negócio em páginas JSP.
No entanto, uma das principais vantagens da maioria dos frameworks MVC não é tão visível: a possibilidade de definir fluxos de navegação, ou seja, descrever conjuntos de passos para os usuários no website de forma estruturada e reusável. Neste cenário, o Struts e o Spring, por exemplo, encapsulam os detalhes a respeito do atendimento das requisições de um usuário e renderizam páginas de resposta por meio da configuração de actions (ou handlers) de acordo com o estado da requisição. Isso pode ser notado quando temos a exibição de uma página de erro ou de sucesso dependendo da ação realizada pelo usuário.
Fluxos simples como o do exemplo citado são perfeitamente gerenciáveis pelos mecanismos nativos da maioria dos frameworks MVC. Contudo, na medida em que a aplicação web se torna maior, com mais páginas ou com mais opções de respostas para as ações disponíveis, gerenciar esses fluxos pode se tornar mais complicado, uma vez que os frameworks MVC mais conhecidos não gerenciam os estados das requisições (vide BOX 1), tornando necessário escrever mais código nos handlers para fazer esse trabalho.
BOX 1. Estado das requisições em aplicações web
Numa aplicação web cada estado de uma requisição representa um propósito específico e, por isso, terá uma página ou handler correspondente. Por exemplo, a submissão de uma página de formulário pode ter quatro ou mais estados possíveis, a saber: o estado inicial (a exibição do formulário em si); aguardando (quando o formulário está sendo submetido, após o clique no botão de submissão); sucesso (que resultará na exibição de uma página de sucesso); ou insucesso (que resultará na exibição de uma página de erro).
Outro aspecto interessante sobre o gerenciamento do fluxo de navegação é que, se a estrutura da aplicação é complexa, ou seja, se os caminhos para chegar às páginas são longos e/ou muito subdivididos, entender o fluxo se torna mais difícil. Nesses casos, seria necessário manter um mapa atualizado das páginas e constantemente verificar o código para entender os fluxos, especialmente aqueles que envolvem condições e múltiplos estados. Ademais, a manutenção de grandes sites pode ser complicada e envolver bastante tempo estudando seus fluxos antes de implementar uma modificação.
Com base nisso, neste artigo vamos explorar uma solução dada pelo Spring para o gerenciamento do fluxo de navegação em websites: o Spring Web Flow. Iniciado como uma extensão do Spring Framework, sua primeira versão estável foi lançada em 2006, no entanto, sua popularização ocorreu apenas com a versão 2, lançada em 2008.
Com o SWF o fluxo de navegação é construído por meio da definição dos estados pelos quais o usuário passa. A partir disso, para chegar em cada um dos estados o usuário dispara eventos. Por sua vez, entre os eventos que fazem as passagens de um estado para outro temos as transições de estado. Todo esse mecanismo é especificado em regras dentro de um arquivo XML, o que viabiliza o reuso e diminui a escrita de código.
No exemplo que apresentaremos nesse artigo focaremos nas principais vantagens do SWF ilustrando as maiores contribuições dadas por esse projeto ao desenvolvimento web, além de demonstrar seu uso de forma integrada com o Spring MVC e Hibernate. Dessa forma, nosso exemplo ficará bastante completo e fornecerá ao leitor todos os passos necessários para iniciar o desenvolvimento com os frameworks citados, assim como algumas noções sobre padrões arquiteturais bastante utilizados em soluções dessa natureza.
Detalhes do projeto exemplo
O projeto será construído com a IDE Eclipse, persistirá dados em um banco de dados MySQL, rodará no Tomcat 8 e sua implementação seguirá o padrão Model-View-Controller, com o suporte do Spring MVC 4.2.2. A arquitetura da aplicação inclui ainda uma camada de serviço rudimentar, responsável por fazer a ligação entre a camada de controle e a camada de dados usando uma simplificação do padrão Service Layer.
A camada de persistência seguirá o padrão DAO e utilizaremos o framework Hibernate ORM 4.3.11 para implementá-la. Já a nossa versão do SWF será a 2.4.0. Além disso, sugerimos o uso do Eclipse em versão superior à 4.4 (Luna, para desenvolvimento Java EE) por já possuir integração nativa com o gerenciador de dependências Maven. Caso essa não seja a sua versão do Eclipse, é necessário instalar o Maven em versão igual ou superior à 3.2 (e/ou um plugin respectivo do Maven para Eclipse) ou gerenciar as dependências do projeto manualmente.
Sendo assim, mais do que demonstrar o uso do SWF, apresentaremos um exemplo completo da integração entre Spring Web Flow, Spring MVC e Hibernate. No entanto, é importante destacar que não é objetivo desse artigo discorrer sobre as arquiteturas e padrões de projeto, bem como sobre os frameworks Spring e Hibernate. Dessa forma, o leitor deve explorar a documentação correspondente ou artigos anteriores publicados nessa revista sobre o uso desses recursos.
Criando o projeto e configurando as dependências
Primeiramente, criemos um Dynamic Web Project no Eclipse, o qual chamaremos de GestaoAcademicaWeb. Para isso, após acessar as opções de menu File > New Project > Other, na janela New Project, selecione Web > Dynamic Web Project, conforme a Figura 1.
Clicando em Next será exibida a janela com as propriedades do projeto (vide Figura 2). Vamos configurá-lo para rodar no container Tomcat 8, no entanto, o leitor deve ficar à vontade para utilizar versões diferentes do Tomcat ou outro container web Java, como Jetty ou JBoss. Feito isso, clique em Finish.
Com o projeto criado, vamos integrá-lo ao gerenciador de dependências Maven. Portanto, clique com o botão direito sobre o projeto e selecione Configure > Convert to Maven Project, como mostra a Figura 3. Será criado, assim, o arquivo pom.xml, onde vamos inserir as dependências do nosso projeto, conforme descrito na Listagem 1.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>GestaoAcademicaWeb</groupId>
<artifactId>GestaoAcademicaWeb</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
</project>
Repare que incluímos nesse arquivo todas as dependências necessárias para utilizar o Spring MVC e o Hibernate ORM, assim como o driver JDBC solicitado para a conexão com o banco de dados. Em seguida, devemos criar também os arquivos \src\hibernate.cfg.xml e spring-context.xml, que encapsulam as configurações do Hibernate e do Spring MVC e cujos códigos-fontes podem ser vistos junto ao código disponível para download na página desta edição.
Em hibernate.cfg.xml serão feitas as configurações correspondentes ao mapeamento objeto-relacional (Vide BOX 2), como veremos ao longo do artigo. Já o arquivo spring-context.xml inclui as configurações do Spring, como o diretório em que as páginas da camada de visão serão armazenadas e, futuramente, configurações do SWF. Também aproveitamos esse arquivo para armazenar as configurações que o Hibernate usa para acessar o banco de dados como, por exemplo, o datasource.
BOX 2. Mapeamento Objeto-Relacional
Mapeamento Objeto-Relacional é a técnica utilizada para tornar possível a representação das classes da aplicação – projetadas a partir do paradigma da Orientação a Objetos – em entidades de um banco de dados relacional – projetadas a partir do paradigma Relacional e da Normalização.
Agora, vamos definir o arquivo \WEB-INF\jdbc.properties para armazenar as configurações de acesso ao banco, como URI, usuário e senha (vide Listagem 2). As propriedades contidas nesse arquivo são, então, chamadas em spring-context.xml.
Neste ponto vale ressaltar que, embora não seja necessário seguir a ordem de criação dos arquivos sugerida nesse artigo, os passos que demonstramos simplificam o desenvolvimento, já que vemos que os arquivos fazem referências uns aos outros em alguns casos.
jdbc.driverClassName= com.mysql.jdbc.Driver
jdbc.dialect=org.hibernate.dialect.MySQLDialect
jdbc.databaseurl=jdbc:mysql://localhost:3306/gestaoacademica
jdbc.username=gestaoacademica
jdbc.password=gestaoacademica
O próximo passo é criar a estrutura do projeto de acordo com os padrões que serão adotados; no nosso caso, uma arquitetura MVC, uma camada de serviço, além do padrão DAO para a camada de persistência. Sendo assim, clique com o botão direito sobre a pasta Java Resources\src, no Package Explorer do Eclipse, e selecione New > Package para criar o pacote-base do projeto. Conforme demonstra a Figura 4, digite “br.com.devmedia.gestaoacademicaweb” no campo Name e clique em Finish.
Dentro do pacote-base vamos repetir o mesmo procedimento para criar os pacotes control e model, além do dao – onde serão armazenadas as classes da camada de persistência da nossa aplicação – e service – onde ficarão as classes da camada de serviço. Nossas páginas (a camada view) ficarão no diretório WEB-INF/views. Por fim, criaremos, também, o diretório WEB-INF/flows, onde armazenaremos os fluxos. Dessa forma, a estrutura do projeto deve ficar como mostrado na Figura 5.
O fluxo da aplicação exemplo
Para começar, lembremos que, com SWF, um fluxo é composto por um conjunto de estados, cada estado terá uma página correspondente e as mudanças de estado e, consequentemente, de página, ocorrem após o usuário disparar eventos. A partir disso, saiba que esses eventos podem levar a dois tipos básicos de estado:
- View-state: representado por uma página na qual o usuário chega espontaneamente, por exemplo, digitando um endereço;
- End-state: representado por uma página na qual o usuário chega como resultado de um evento, como clicar em um link ou como resposta a uma submissão de formulário.
Mais além, os handlers responsáveis pelas submissões de formulários são representados como um “sub-estado” chamado action-state, que existe entre view-state e um end-state.
Como nesse artigo criaremos um cadastro de docentes de uma universidade cujo fluxo é bastante simples, especificamos apenas três estados (dois view-states e um end-state), como segue:
- View-state 1, representado pela página de listagem;
- View-state 2, representado pelo formulário de cadastro de docentes;
- End-state, representado pela página de resposta ao cancelamento.
A Figura 6 demonstra o fluxo do nosso projeto exemplo, ilustrando seus estados e transições.
Podemos ver que entre os estados temos as transições “novo”, “excluir”, “cancelar” e “inserir”. Isso significa que da página inicial – a listagem de docentes – temos eventos que levam o usuário para incluir novo docente ou excluir docentes, ambos por meio de cliques em links. Se escolher incluir um docente, o usuário será direcionado para o formulário de cadastro. E se escolher excluir um docente, o usuário é mantido na página com a listagem de docentes após a operação.
Uma vez na página do formulário de docentes o usuário pode optar por inserir o docente ou cancelar a operação. Se optar por inserir, após a operação de cadastro ser realizada com sucesso o usuário é direcionado de volta à listagem. Se escolher cancelar, é direcionado para uma página de resposta.
Implementação do projeto exemplo
Nas seções anteriores descrevemos todos os conceitos necessários à construção do nosso projeto com o SWF. A partir de agora, partiremos para a implementação da aplicação exemplo, começando pelas camadas de modelo e persistência e depois pelas camadas de serviço e controle. Na sequência, construiremos o fluxo com o SWF, objeto principal desse artigo.
As camadas de modelo e persistência
Vamos iniciar a nossa implementação criando, dentro do pacote model, a classe Docente,único JavaBean do nosso projeto. Sendo assim, com o botão direito sobre o pacote, selecione a opção New > Class. Em seguida, digite o nome Docente e clique em Finish. O código-fonte desse artefato pode ser visto na Listagem 3. Nele, podemos observar anotações (vide BOX 3) do Hibernate que associam esta classe/entidade à sua respectiva tabela no banco de dados (através da anotação @Table). Como podemos observar também, cada atributo da classe é associado a um campo em sua respectiva tabela, por meio da anotação @Column. Finalizando, vamos configurar o mapeamento objeto-relacional de Docente no arquivo hibernate.cfg.xml, para habilitar o Hibernate a persistir os objetos no banco de dados, o que pode ser feito com o código da Listagem 4.
BOX 3. Anotações
Anotações são recursos para a declaração de metadados – dados que descrevem outros dados – úteis para localizar dependências, configurações ou para fazer verificações lógicas. Essas definições serão, então, interpretadas pelo compilador para realizar uma determinada tarefa.
package br.com.devmedia.gestaoacademicaweb.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="DOCENTES")
public class Docente {
@Id
@Column(name="ID")
@GeneratedValue
private Integer id;
@Column(name="NOME")
private String nome;
@Column(name="MATRICULA")
private String matricula;
@Column(name="TITULACAO")
private String titulacao;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getMatricula() {
return matricula;
}
public void setMatricula(String matricula) {
this.matricula = matricula;
}
public String getTitulacao() {
return titulacao;
}
public void setTitulacao(String titulacao) {
this.titulacao = titulacao;
}
}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
</session-factory>
</hibernate-configuration>
<mapping class="br.com.devmedia.gestaoacademicaweb.model.Docente" />
</session-factory>
</hibernate-configuration>
Com o JavaBean da camada de modelo criado, podemos implementar a camada de persistência, que consiste em uma interface (DocenteDAO) e sua implementação (DocenteDAOImpl). A interface DocenteDAO declara os métodos para inserir e excluir registros do tipo Docente no banco de dados (adicionarDocente() e removerDocente(), respectivamente), bem como para recuperá-los em uma operação de listagem (listarDocentes()). Como a classe DocenteDAOImpl implementa a interface DocenteDAO, vemos em seu corpo a implementação dos métodos que realizam as operações com o banco de dados. Nesses métodos o Hibernate é chamado para realizar as operações de inserção (por meio do método save()), exclusão (com o método delete()), e listagem (através do método list()), como mostra o código-fonte visto nas Listagens 5 e 6.
package br.com.devmedia.gestaoacademicaweb.dao;
import java.util.List;
import br.com.devmedia.gestaoacademicaweb.model.Docente;
public interface DocenteDAO {
public void adicionarDocente(Docente docente);
public void removerDocente(int id);
public List<Docente> listarDocentes();
}
package br.com.devmedia.gestaoacademicaweb.dao;
import java.util.List;
import br.com.devmedia.gestaoacademicaweb.model.Docente;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class DocenteDAOImpl implements DocenteDAO {
@Autowired
private SessionFactory sessionFactory;
public void adicionarDocente(Docente docente) {
sessionFactory.getCurrentSession().save(docente);
}
public void removerDocente(int id) {
Docente docente = (Docente) sessionFactory.getCurrentSession().load(Docente.class, id);
if (null != docente) {
sessionFactory.getCurrentSession().delete(docente);
}
}
public List<Docente> listarDocentes() {
return sessionFactory.getCurrentSession().createQuery("from Docente").list();
}
}
}
As camadas de serviço e controle
Após implementarmos o modelo e a persistência vamos codificar uma pequena camada de serviço para fazer com que a aplicação fique “magra” e possibilite que as regras de negócio sejam encapsuladas em camadas mais simples e reusáveis. Isso ocorre na medida em que os serviços assumem a responsabilidade de fazer as chamadas à camada de persistência, deixando assim a lógica de negócio encapsulada nos controllers.
No nosso exemplo implementaremos a camada de serviço com uma simplificação do padrão Service Layer (BOX 4). Para isso, crie dois arquivos no pacote br.com.devmedia.gestaoacademicaweb.service: a interface DocenteService e sua implementação, a classe DocenteServiceImpl. Vemos, então, nos métodos adicionarDocente(), removerDocente() e listar(), chamadas às respectivas operações na camada de persistência, como demonstram as Listagens 7 e 8, e vemos também que a classe é declarada com @Service, anotação do Spring utilizada para descrevê-la como parte da camada de serviço da aplicação.
BOX 4. Padrão Service Layer
Service Layer é um padrão de projeto orientado a objetos que visa organizar a lógica de negócio em serviços que, devidamente categorizados, compartilham funcionalidades. Dessa forma, o esforço conceitual de manutenção e gerenciamento destas é reduzido, uma vez que os módulos da aplicação passam a compartilhar funcionalidades comuns encapsuladas nos serviços.
package br.com.devmedia.gestaoacademicaweb.service;
import java.util.List;
import br.com.devmedia.gestaoacademicaweb.Docente;
public interface DocenteService {
public void adicionarDocente(Docente docente);
public void removerDocente(int id);
public List<Docente> listarDocentes();
}
package br.com.devmedia.gestaoacademicaweb.service;
import java.util.List;
import br.com.devmedia.gestaoacademicaweb.Docente;
import br.com.devmedia.gestaoacademicaweb.dao.DocenteDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DocenteServiceImpl implements DocenteService {
@Autowired
private DocenteDAO docenteDAO;
@Transactional
public void adicionarDocente(Docente docente) {
docenteDAO.adicionarDocente(docente);
}
@Transactional
public void removerDocente(int id) {
docenteDAO.removerDocente(id);
}
@Transactional
public List<Docente> listarDocentes() {
return docenteDAO.listarDocentes();
}
}
Por sua vez, os dados a serem persistidos devem ser recebidos na camada de controle, responsável por dar o tratamento correspondente às requisições de acordo com as regras de negócio definidas. Vamos então criar a camada de controle no pacote control,começando pela classe DocenteController, cujo código pode ser visto na Listagem 9.
package br.com.devmedia.gestaoacademicaweb.control;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import br.com.devmedia.gestaoacademicaweb.model.Docente;
import br.com.devmedia.gestaoacademicaweb.service.DocenteService;
@Controller
public class DocenteController {
@Autowired
private DocenteService docenteService;
public List<Docente> listarDocentes() {
List<Docente> docentes = docenteService.listarDocentes();
return docentes;
}
public void adicionarDocente(Docente docente) {
docenteService.adicionarDocente(docente);
}
public void removerDocente(int id) {
docenteService.removerDocente(id);
}
}
Observe nessa listagem que os métodos acessam a camada de serviço para realizar as operações com o banco de dados, como mostra a instrução docenteService.adicionarDocente(docente).
Implementando o fluxo com o Spring Web Flow
Com as camadas de modelo, controle, serviço e persistência concluídas, já é possível implementar o fluxo da aplicação, bem como a camada de visão, que conterá as páginas que corresponderão aos estados declarados no fluxo.
Para manter o código estruturado – além de permitir um eventual reuso dos fluxos criados – recomendamos a criação de um arquivo XML para cada fluxo da aplicação. No nosso caso armazenaremos estes arquivos XML dentro de WEB-INF/flows.
Dito isso, dentro do diretório flows crie o arquivo inserirDocente.xml, o que é feito clicando com o botão direito sobre este diretório e selecionando New > File. Uma vez que é necessário realizar as configurações correspondentes ao SWF no arquivo spring-context.xml, ainda não alteraremos o XML recém-criado. Vamos, agora, modificar as configurações do Spring alterando seu arquivo de configuração para que fique conforme demonstrado na Listagem 10.
<?xml version="1.0" encoding="UTF-8"?>
...
<context:annotation-config />
<context:component-scan base-package="br.com.devmedia.gestaoacademicaweb" />
<bean id="jspViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="/WEB-INF/jdbc.properties" />
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseurl}" p:username="${jdbc.username}"
p:password="${jdbc.password}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<webflow:flow-executor id="flowExecutor"/>
<webflow:flow-registry id="flowRegistry">
<webflow:flow-location path="/WEB-INF/flows/inserirDocente.xml" id="inserirDocente" />
</webflow:flow-registry>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry"/>
<property name="order" value="0"/>
</bean>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
<bean name="docenteController" class="br.com.devmedia.gestaoacademicaweb.control.DocenteController" />
</beans>
As principais modificações que fizemos em spring-context.xml para configurar o SWF se referem ao mapeamento dos fluxos que nosso website possui. No nosso caso chamamos o fluxo de inserirDocente. Podemos verificar sua descrição no nó <webflow:flow-registry>, onde há a declaração <webflow:flow-location path="/WEB-INF/flows/inserirDocente.xml" id="inserirDocente />, que especifica que o fluxo identificado por inserirDocente está declarado no arquivo de mesmo nome, com extensão XML, localizado em /WEB-INF/flows/.
Seguindo em frente, declaramos os controllers da nossa aplicação utilizando a tag <bean>, como verificado no trecho <bean name="docenteController" class="br.com.devmedia.gestaoacademicaweb.control.DocenteController" />. Vale lembrar que no nosso exemplo temos apenas um controller, que será identificado pelo nome docenteController.
No mais, foram incluídas em spring-context.xml as configurações padrão do SWF, especialmente das classes FlowHandlerMapping e FlowHandlerAdapter, os chamados flow handlers do Spring Web Flow.
Desenvolvendo as páginas da camada de visão
Antes de iniciar o desenvolvimento do fluxo, vamos criar as páginas da nossa camada de visão: um formulário para cadastro de Docentes (inserir_docente_form.jsp); uma página que exibirá uma lista dos docentes cadastrados (listar_docente.jsp); e a página de resposta ao cancelamento (cancelado.jsp). Todas serão armazenadas no diretório WEB-INF/views.
Dito isso, com o botão direito sobre o diretório WEB-INF/views, selecione New > JSP File. Na janela de propriedades do arquivo, digite no campo File Name o nome “inserir_docente_form.jsp” e clique em Finish. O código-fonte do formulário de cadastro de docentes pode ser visto na Listagem 11, enquanto é possível visualizar sua aparência na Figura 7.
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<title>Cadastro de Docentes</title>
</head>
<body>
<h3>Formulário de Cadastro de Docentes</h3>
<form:form method="post" modelAttribute="docenteModel">
<table>
<tr>
<td>Nome:</td>
<td><form:input path="nome" /></td>
</tr>
<tr>
<td>Matrícula:</td>
<td><form:input path="matricula" /></td>
</tr>
<tr>
<td>Titulação:</td>
<td><form:input path="titulacao" /></td>
</tr>
<tr>
<td>
<input type="submit" name="_eventId_inserir" value="Salvar"/>
</td>
<td>
<input type="submit" name="_eventId_cancelar" value="Cancelar"/>
</td>
</tr>
</table>
</form:form>
</body>
</html>
Para construir esse formulário fizemos uso dos pacotes de taglibs do Spring; em especial, do pacote form, como é mostrado nos trechos de código iniciados por <form:input>. Essa taglib é utilizada para criar os campos do formulário conforme verificado em <form:input path="nome" />. Já a associação entre os formulários e as respectivas classes na camada de modelo é feita por meio da propriedade modelAttribute na tag <form>,como vemos em <form:form method="post" modelAttribute="docenteModel">. Nesse trecho constatamos que o formulário de cadastro de docente é associado à classe da camada de modelo identificada por docenteModel.
Essa identificação é configurada no arquivo do fluxo, e a veremos mais adiante, quando apresentarmos o conteúdo do arquivo inserirDocente.xml. Nesse caso, a propriedade path é usada nas declarações dos campos para indicar a que atributo da classe cada campo de formulário será associado.
A passagem de uma página para outra – que o SWF identifica como transição entre estados – é feita por meio do disparo de eventos pelo usuário, ou seja, quando este clica em um botão, ou num link, por exemplo. Com isso, cada evento disponível nas páginas deve ser identificado (na sua propriedade name) pelo prefixo _eventId na declaração do elemento de página que foi construído para dispará-lo. Por exemplo, o botão Salvar no formulário de cadastro de docentes é identificado por _eventId_inserir, como pode ser visto no trecho <input type="submit" name="_eventId_inserir" value="Salvar"/>.
Agora vamos criar os arquivos cancelado.jsp e listar_docentes.jsp, que representam, respectivamente, a página de resposta de cancelamento e a listagem dos docentes cadastrados. O código da página de resposta de cancelamento é bastante simples e é apresentado na Listagem 12. Já o código-fonte da listagem de docentes é demonstrado Listagem 13, enquanto sua aparência pode ser vista na Figura 8.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>Processo de cadastramento cancelado</h3>
</body>
</html>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Listagem de Docentes</title>
</head>
<body>
<h3>Docentes Cadastrados</h3>
<h3><a href="$&_eventId=novo">+ Novo Docente</a></h3>
<c:if test="${!empty docenteList}">
<table border="1">
<tr>
<th>Nome</th>
<th>Matrícula</th>
<th>Titulação</th>
<th> </th>
</tr>
<c:forEach items="$" var="docente">
<tr>
<td>${docente.nome} </td>
<td>${docente.matricula}</td>
<td>${docente.titulacao}</td>
<td><a href="$&_eventId=excluir&docenteId=${docente.id}">Excluir</a></td>
</tr>
</c:forEach>
</table>
</c:if>
</body>
</html>
No código dessa página os eventos disponíveis são cliques nos links para “novo docente” e “excluir”. Aqui é importante lembrar que os eventos devem ser identificados pelo prefixo _eventId, e isso é implementado na declaração dos links, como em <a href="$&_eventId=excluir&docenteId=${docente.id}">.
Note também que incluímos no link o fluxo a que esse evento se refere (no nosso caso, inserirDocente), por meio do parâmetro flowExecutionUrl. O restante da página utiliza taglibs JSTL para percorrer uma coleção e exibir os registros de docente cadastrados no banco – devidamente recuperados pelas demais camadas da aplicação.
Agora que as páginas foram criadas podemos fazer a implementação do fluxo da aplicação modificando o arquivo inserirDocente.xml, como demonstrado na Listagem 14. O primeiro elemento que devemos notar nesse arquivo é a declaração da variável docenteModel, feita no trecho <var name="docenteModel" class="br.com.devmedia.gestaoacademicaweb.model.Docente"/>. Essa variável é utilizada para descrever a associação entre a classe Docente na camada de modelo e o formulário de cadastro de docentes.
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="docenteModel" class="br.com.devmedia.gestaoacademicaweb.model.Docente"/>
<view-state id="form" view="/WEB-INF/views/inserir_docente_form.jsp" model="docenteModel">
<transition on="inserir" to="inserirDocenteAction" />
<transition on="cancelar" to="cancelado" />
</view-state>
<action-state id="inserirDocenteAction">
<evaluate expression="docenteController.adicionarDocente(docenteModel)" />
<transition to="listar" />
</action-state>
<view-state id="listar" view="/WEB-INF/views/listar_docentes.jsp">
<on-render>
<evaluate expression="docenteController.listarDocentes()" result="viewScope.docenteList"/>
</on-render>
<transition on="novo" to="form" />
<transition on="excluir" to="listar">
<set name="requestScope.docenteId" value="requestParameters.docenteId" type="long" />
<evaluate expression="docenteController.removerDocente(docenteId)" />
</transition>
</view-state>
<end-state id="cancelado" view="/WEB-INF/views/cancelado.jsp"/>
</flow>
Como demonstrado nessa listagem, os estados do fluxo são descritos em nós XML com o sufixo -state. Visto que nosso projeto possui dois view-states (listagem de docentes e formulário de cadastro de docentes) e um end-state (operação cancelada), utilizamos os nós <view-state> e <end-state> para declarar esses estados.
Por sua vez, especificamos o handler do formulário de cadastro de docentes com o nó <action-state>, no trecho <action-state id="inserirDocenteAction">, e definimos as transições em nós <transition>.A propriedade on de uma transição indica em que evento ela deve ser disparada; enquanto a propriedade to, para qual estado o usuário é direcionado. Por exemplo, na marcação <transition on="cancelar" to="cancelado" />, uma vez disparado o evento cancelar o usuário será encaminhado para o estado cancelado.
Para o evento de submissão do formulário, declaramos uma transição como <transition on="inserir" to="inserirDocenteAction"/>. Nesse trecho fazemos com que o action-state inserirDocenteAction seja acionado quando o evento inserir for disparado (podemos ver na Listagem 11 que esse evento está associado ao clique no botão Salvar). Já a operação adicionarDocente(), da classe DocenteController, será realizada durante o action-state inserirDocenteAction, o que viabilizará a inserção dos dados do formulário de cadastro de docentes no banco.
Para isso, especificamos o nó <evaluate>, como mostra o trecho <evaluate expression="docenteController.adicionarDocente(docenteModel)"/>. Esse recurso é utilizado novamente na listagem de docentes, conforme a marcação <evaluate expression="docenteController.listarDocentes()" result="viewScope.docenteList"/>, que faz com que o método listarDocentes() seja executado na renderização da listagem de docentes.
Nesse segundo exemplo vemos outro recurso do SWF ser utilizado: o retorno do método é armazenado em uma variável que pode ser acessada na camada de visão. A propriedade result da tag <evaluate> define o armazenamento do retorno do método listarDocentes() em uma variável chamada docenteList. Nesse trecho, note que também é indicado o escopo da variável (viewScope, que significa que a variável estará disponível apenas no estado em que ela foi declarada; no caso, a view-state listar). Outros escopos existem, mas não os exploramos nesse artigo.
Com isso, a última coisa que falta para nosso projeto ser concluído é criar o banco de dados. Para tanto, vamos usar o script de criação da tabela descrito na Listagem 15. Com isso nosso exemplo está pronto para ser testado, o que pode ser feito após iniciar o Tomcat e digitar, no navegador, a URL http://localhost:8080/inserirDocente.
CREATE TABLE DOCENTES (
ID INT PRIMARY KEY AUTO_INCREMENT,
NOME VARCHAR(100),
MATRICULA VARCHAR(100),
TITULACAO VARCHAR(100)
);
Ivar Jacobson, GradyBooch e JamesRumbaugh, que podemos chamar de pais da Unified Modeling Language (UML), afirmam que um “estado” é o momento durante o ciclo de vida de um objeto no qual ele satisfaz algumas condições, executa algumas atividades ou espera por eventos. Nesse artigo, vemos como esse conceito é adotado para descrever a navegação do usuário, mais especificamente, as páginas que ele pode visitar e os eventos que dispara pelo caminho.
Esse conceito tornou possível o encapsulamento do fluxo, diminuindo a escrita de código, deixando a estrutura de navegação do website mais clara e, eventualmente, permitindo que os fluxos sejam reutilizados não só em outras partes do próprio site, mas em outras aplicações. Vale destacar que o código, especialmente das camadas de controle e visão, está mais simples do que o construído usando outros frameworks MVC, como o Struts, ou o próprio Spring, na medida em que menos código precisou ser escrito.
Também é importante ressaltar que o SWF é perfeitamente integrável a outros frameworks MVC. No entanto, sua integração com o Spring MVC é mais interessante, até pela obviedade de fazer parte do mesmo projeto, assim como pelo mecanismo de injeção de dependência do Spring, algo bastante celebrado na comunidade de desenvolvedores.
No que diz respeito à estrutura do projeto, o fato dos fluxos serem organizados em um arquivo XML e identificados de forma única constitui mais uma vantagem do SWF, especialmente por deixar a estrutura da aplicação visível e viabilizar o reuso. Outra vantagem é a possibilidade de organizar os fluxos em subfluxos de maneira transparente, separando toda a lógica de negócio da lógica de navegação, uma inovação no que diz respeito ao padrão Model-View-Controller.
Sendo assim, os recursos do SWF devem ser explorados de forma bastante ampla para obter um melhor gerenciamento do workflow na plataforma web, o que nos possibilita afirmar que o SWF é ideal para construir aplicações web em que as regras de negócio demandam guiar o usuário por fluxos bem controlados, estruturados e, ocasionalmente, reusáveis.
Links
- Site oficial do projeto Spring Web Flow
- Site oficial do projeto Spring
- text
- Site oficial do Apache Maven
Confira também
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo