02 VRaptor3 - O guia inicial de 10 minutos
Começando o projeto: uma loja virtual
Vamos começar baixando o vraptor-blank-project em site do VRaptor. Esse blank-project já possui a configuração no web.xml e os JARs no WEB-INF/lib necessários para começar a desenvolver no VRaptor. Como você pode ver, a única configuração necessária no web.xml é o filtro do VRaptor:
<filter>
<filter-name>vraptor</filter-name>
<filter-class>br.com.caelum.vraptor.VRaptor</filter-class>
</filter>
<filter-mapping>
<filter-name>vraptor</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Você pode facilmente importar esse projeto no Eclipse, e rodá-lo clicando com o botão da direita e escolhendo Run as... / Run on server.... Escolha então um servlet container (ou faça o setup de um novo) e então acesse http://localhost:8080/vraptor-blank-project/. Você pode escolher, nas propriedades do projetor, dentro de Web Project Settings, o nome do contexto para algo melhor, como onlinestore. Agora, se você rodar esse exemplo deve ser possível acessar http://localhost:8080/onlinestore e ver It works! no navegador.
Nota
Se você está usando um container de servlet 3.0 (java EE 6), você nem precisa de web.xml, pois o VRaptor vai configurar esse filtro por meio do novo recurso de web-fragments.Um cadastro de produtos
Para começar o sistema, vamos começar com cadastro de produtos. Precisamos de uma classe que vai representar o produto, e vamos usá-la para guardar produtos no banco, usando o Hibernate:
@Entity
public class Produto {
@Id
@GeneratedValue
private Long id;
private String nome;
private String descricao;
private Double preco;
// getter e setters e métodos de negócio que julgar necessário
}
Precisamos também de uma classe que vai controlar o cadastro de produtos, tratando requisições web. Essa classe vai ser o Controller de produtos:
public class ProdutosController {
}
A classe ProdutosController vai expor URIs para serem acessadas via web, ou seja, vai expor recursos da sua aplicação. E, para indicar isso, você precisa anotá-la com com @Resource:
@Resource
public class ProdutosController {
}
Ao colocar essa anotação na classe, todos os métodos públicos dela serão acessíveis pela web. Por exemplo, se tivermos um método lista na classe:
@Resource
public class ProdutosController {
public List<Produto> lista() {
return new ArrayList<Produto>();
}
}
o VRaptor automaticamente redirecionará todas as requisições à URI /produtos/lista para esse método. Ou seja, a convenção para a criação de URIs é /
<ul>
<c:forEach items="${produtoList}" var="produto">
<li> ${produto.nome} - ${produto.descricao} </li>
</c:forEach>
</ul>
A convenção para o nome dos atributos exportados é bastante intuitiva: se for uma collection, como o caso do método acima, o atributo será
Criando o ProdutoDao: injeção de Dependências
O VRaptor usa fortemente o conceito de Injeção de Dependências e Inversão de Controle. A ideia é que você não precisa criar ou buscar as dependências da sua classe, você simplesmente as recebe e o VRaptor se encarrega de criá-las pra você. Mais informações no capítulo de Injeção de Dependências. Estamos retornando uma lista vazia no nosso método lista. Seria muito mais interessante retornar uma lista de verdade, por exemplo todas os produtos cadastrados no sistema. Para isso vamos criar um DAO de produtos, para fazer a listagem:
public class ProdutoDao {
public List<Produto> listaTodos() {
return new ArrayList<Produto>();
}
}
E no nosso ProdutosController podemos usar o dao pra fazer a listagem de produtos:
@Resource
public class ProdutosController {
private ProdutoDao dao;
public List<Produto> lista() {
return dao.listaTodos();
}
}
Podemos instanciar o ProdutoDao direto do controller, mas é muito mais interessante aproveitar o gerenciamento de dependências que o VRaptor faz e receber o DAO no construtor! E, para que isso seja possível, basta anotar o DAO com @Component e o VRaptor vai se encarregar de criá-lo e injetá-lo na sua classe:
@Component
public class ProdutoDao {
//...
}
@Resource
public class ProdutosController {
private ProdutoDao dao;
public ProdutosController(ProdutoDao dao) {
this.dao = dao;
}
public List<Produto> lista() {
return dao.listaTodos();
}
}
Formulário de adição: redirecionamento
Temos uma listagem de Produtos, mas ainda não temos como cadastrá-los. Vamos então criar um formulário de adição de produtos. Para não ter que acessar o JSP diretamente, vamos criar uma lógica vazia que só redireciona para o JSP:
@Resource
public class ProdutosController {
//...
public void form() {
}
}
Podemos acessar o formulário, que estará em /WEB-INF/jsp/produtos/form.jsp, pela URI /produtos/form:
<form action="<c:url value='/produtos/adiciona'/>">
Nome: <input type="text" name="produto.nome" /><br/>
Descrição: <input type="text" name="produto.descricao" /><br/>
Preço: <input type="text" name="produto.preco" /><br/>
<input type="submit" value="Salvar" />
</form>
Como o formulário vai salvar o Produto pela URI /produtos/adiciona, precisamos criar esse método no nosso controller:
@Resource
public class ProdutosController {
//...
public void adiciona() {
}
}
Repare nos nomes dos nossos inputs: produto.nome, produto.descricao e produto.preco. Se recebermos um Produto no método adiciona com o nome produto, o VRaptor vai popular os seus campos nome, descricao e preco, usando os seus setters no Produto, com os valores digitados nos inputs. Inclusive o campo preco, vai ser convertido para Double antes de ser setado no produto. Veja mais sobre isso no capítulo de converters. Então, usando os nomes corretamente nos inputs do form, basta criar seu método adiciona:
@Resource
public class ProdutosController {
//...
public void adiciona(Produto produto) {
dao.adiciona(produto);
}
}
Geralmente, depois de adicionar algo no sistema queremos voltar para a sua listagem, ou para o formulário novamente. No nosso caso, queremos voltar pra listagem de produtos ao adicionar um produto novo. Para isso existe um componente do VRaptor: o Result. Ele é responsável por adicionar atributos na requisição, e por mudar a view a ser carregada. Se eu quiser uma instância de Result, basta recebê-lo no construtor:
@Resource
public class ProdutosController {
public ProdutosController(ProdutoDao dao, Result result) {
this.dao = dao;
this.result = result;
}
}
E para redirecionar para a listagem basta usar o result:
result.redirectTo(ProdutosController.class).lista();
Podemos ler esse código como: Como resultado, redirecione para o método lista do ProdutosController. A configuração de redirecionamento é 100% java, sem strings envolvidas! Fica explícito no seu código que o resultado da sua lógica não é o padrão e qual resultado você está usando! Você não precisa ficar se preocupando com arquivos de configuração! Mais ainda, se eu quiser mudar o nome do método lista, eu não preciso ficar rodando o sistema inteiro procurando onde estão redirecionando pra esse método, basta usar o refactor do eclipse, por exemplo, e tudo continua funcionando! Então nosso método adiciona ficaria:
public void adiciona(Produto produto) {
dao.adiciona(produto);
result.redirectTo(ProdutosController.class).lista();
}
Veja mais informações sobre o Result no capítulo Views e Ajax.
Validação
Não faz muito sentido adicionar um produto sem nome no sistema, nem um produto com preço negativo. Antes de adicionar o produto, precisamos verificar se é um produto válido, com nome e preço positivo, e caso não seja válido voltamos para o formulário com mensagens de erro. Para fazermos isso, podemos usar um componente do VRaptor chamado Validator. Você pode recebê-lo no construtor do seu Controller, e usá-lo da seguinte maneira:
@Resource
public class ProdutosController {
public ProdutosController(ProdutoDao dao, Result result, Validator validator) {
//...
this.validator = validator;
}
public void adiciona(Produto produto) {
validator.checking(new Validations() { {
that(!produto.getNome().isEmpty(), "produto.nome", "nome.vazio");
that(produto.getPreco() > 0, "produto.preco", "preco.invalido");
} });
validator.onErrorUsePageOf(ProdutosController.class).form();
dao.adiciona(produto);
result.redirectTo(ProdutosController.class).lista();
}
}
Podemos ler as validações da seguinte maneira: Valide que o nome do produto não é vazio e que o preço do produto é maior que zero. Se acontecer um erro, use como resultado a página do form do ProdutosController. Ou seja, se por exemplo o nome do produto for vazio, vai ser adicionada a mensagem de erro para o campo "produto.nome", com a mensagem "nome.vazio" internacionalizada. Se acontecer algum erro, o sistema vai voltar pra página do formulário, com os campos preenchidos e com mensagens de erro que podem ser acessadas da seguinte maneira:
<c:forEach var="error" items="${errors}">
${error.category} ${error.message}<br />
</c:forEach>
Veja mais informações sobre o Validator no capítulo de Validações.
Com o que foi visto até agora, você já consegue fazer 90% da sua aplicação! As próximas sessões desse tutorial mostram a solução para alguns problemas frequentes que estão nos outros 10% da sua aplicação.
Usando o Hibernate para guardar os Produtos
Agora vamos fazer uma implementação de verdade do ProdutoDao, usando o Hibernate para persistir os produtos. Para isso, nosso ProdutoDao precisa de uma Session. Como o VRaptor usa injeção de dependências, basta receber uma Session no construtor!
@Component
public class ProdutoDao {
private Session session;
public ProdutoDao(Session session) {
this.session = session;
}
public void adiciona(Produto produto) {
session.save(produto);
}
//...
}
O VRaptor precisa saber como criar essa Session, e eu não posso simplesmente colocar um @Component na Session pois é uma classe do Hibernate! Para isso existe a interface ComponentFactory, que você pode usar pra criar uma Session.
Veja mais informações de como fazer ComponentFactories no capítulo de Componentes. Você pode, ainda, usar os ComponentFactories que já estão disponíveis para isso no VRaptor, como mostra o capítulo de Utils.
Controlando transações: Interceptors
Muitas vezes, queremos interceptar todas as requisições (ou uma parte delas) e executar alguma lógica, como acontece com o controle de transações. Para isso, existem os Interceptors no VRaptor.
Saiba mais sobre eles no capítulo de Interceptors. Existe um TransactionInterceptor já implementado no VRaptor, saiba como usá-lo no capítulo de Utils.
Carrinho de compras: Componentes na sessão
Se quisermos criar um carrinho de compras no nosso sistema, precisamos de alguma forma manter os itens do carrinho na Sessão do usuário. Para fazer isso, podemos criar um componente que está no escopo de sessão, ou seja, ele vai ser único na sessão do usuário. Para isso, basta criar um componente anotado com @SessionScoped:
@Component
@SessionScoped
public class CarrinhoDeCompras {
private List<Produto> itens = new ArrayList<Produto>();
public List<Produto> getTodosOsItens() {
return itens;
}
public void adicionaItem(Produto item) {
itens.add(item);
}
}
Como esse carrinho de compras é um componente, podemos recebê-lo no construtor do controller que vai cuidar do carrinho de compras:
@Resource
public class CarrinhoController {
private final CarrinhoDeCompras carrinho;
public CarrinhoController(CarrinhoDeCompras carrinho) {
this.carrinho = carrinho;
}
public void adiciona(Produto produto) {
carrinho.adicionaItem(produto);
}
public List<Produto> listaItens() {
return carrinho.getTodosOsItens();
}
}
Além do escopo de sessão existe o escopo de Aplicação com a anotação @ApplicationScoped. Os componentes anotados com @ApplicationScoped serão criados apenas uma vez em toda a aplicação.
Um pouco de REST
Seguindo a idéia REST de que URIs devem identificar recursos na rede para então podermos fazer valer as diversas vantagens estruturais que o protocolo HTTP nos proporciona, note o quão simples fica mapear os diversos métodos HTTP para a mesma URI, e com isso invocar diferentes métodos. Por exemplo, queremos usar as seguintes URIs para o cadastro de produtos:
GET /produtos - lista todos os produtos
POST /produtos - adiciona um produto
GET /produtos/{id} - visualiza o produto com o id passado
PUT /produtos/{id} - atualiza as informações do produto com o id passado
DELETE /produtos/{id} - remove o produto com o id passado
Para criar esse comportamento REST no VRaptor, podemos usar as anotações @Path - que muda qual é a URI que vai acessar o determinado método, e as anotações com os nomes dos métodos HTTP @Get, @Post, @Delete, @Put, que indicam que o método anotado só pode ser acessado se a requisição estiver com o método HTTP indicado. Então, uma versão REST do nosso ProdutosController seria:
public class ProdutosController {
//...
@Get @Path("/produtos")
public List<Produto> lista() {...}
@Post("/produtos")
public void adiciona(Produto produto) {...}
@Get("/produtos/{produto.id}")
public void visualiza(Produto produto) {...}
@Put("/produtos/{produto.id}")
public void atualiza(Produto produto) {...}
@Delete("/produtos/{produto.id}")
public void remove(Produto produto) {...}
}
Note que podemos receber parâmetros nas URIs. Por exemplo, se chamarmos a URI GET /produtos/5, o método visualiza será invocado, e o parâmetro produto vai ter o id populado com 5. Mais informações sobre isso no capítulo de Resources-REST.
Arquivo de mensagens
Internacionalização (i18n) é um recurso poderoso, e que está presente em quase todos os frameworks Web, hoje em dia. E não é diferente no VRaptor3. Com i18n podemos fazer com que nossa aplicação suporte várias línguas (francês, português, espanhol, inglês, etc) de uma maneira que não nos cause muito esforço, bastando apenas fazermos a tradução das mensagens da nossa aplicação. Para isso, é só criarmos um arquivo chamado messages.properties e disponibilizá-lo no classpath da nossa aplicação (WEB-INF/classes). Esse arquivo conterá várias linhas compostas por um conjunto de chave/valor, como por exemplo:
campo.nomeUsuario = Nome de Usuário
campo.senha = Senha
Até então está fácil, mas e se quisermos criar esses arquivos para várias línguas, como por exemplo, inglês? Simples, basta criarmos um outro arquivo properties chamado messages_en.properties. Repare no sufixo _en no nome do arquivo. Isso indica que quando o usuário acessar sua aplicação em uma máquina configurada com locale em inglês as mensagens desse arquivo serão utilizadas. O conteúdo desse arquivo então ficaria:
campo.nomeUsuario = Username
campo.senha = Password
Repare que as chaves são mantidas, mudando apenas o valor para a língua escolhida. Para usar essas mensagens em seus arquivos JSP, você pode utilizar a JSTL. Dessa forma, o código ficaria:
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<body>
<fmt:message key="campo.usuario" /> <input name="usuario.nomeUsuario" />
<br />
<fmt:message key="campo.senha" /> <input type="password" name="usuario.senha" />
<input type="submit" />
</body>
</html>