Iniciando com VRaptor 3

Atualizado em 20 de Novembro de 2010.

Pra quem esta acompanhando a área de Java não é novidade alguma que o VRaptor esta explodindo de sucesso e boas referências. Hoje trabalho em vários projetos na Giran utilizando-o, e posso dizer que estou super satisfeito.

Conhecendo a framework

O VRaptor é um framework MVC que trabalha com os métodos de seus controllers de forma exposta e de maneira RESTFul, ou seja, conseguimos acessar um método público, por exemplo, através da URI: /usuario/cadastrar de forma fácil e intuitiva. A seguir há uma lista resumida de algumas características:

  • Injeção de Dependência;
  • Cast automático;
  • Conversores;
  • Interceptadores;
  • Integração com Spring, Hibernate e JPA;
  • Facilidade de Testes de Unidade;
  • Validações;
  • Redirecionamentos;
  • URI parametrizada;
  • Entre outras mais…

E o melhor de tudo, é open source, brasileira e tem uma lista de discussão com pessoas dispostas a ajudar, além de poder acompanhar o desenvolvimento da framework.

Objetivo

Criar um CRUD de Usuário utilizando a última versão do VRaptor 3 simulando a persistência no banco.

Configurando o projeto:

Depois de fazer o download do (blank project) e descompactar o arquivo vraptor-blank-project-3.x.x.zip na sua workspace, basta importar o projeto no Eclipse, adicioná-lo ao seu servidor e ele estará pronto para rodar.

O projeto vem com alguns arquivos para ser executado no NetBeans também, caso você não o utilize, precisará manter apenas as pastas Webcontent e src, podendo remover o restante.

Criando o controller

Criaremos o pacote br.com.wbotelhos.controller e dentro deste pacote um IndexController. (o blank project já contém um como exemplo)

@Resource
public class IndexController {

    @Path("/")
    public void index() {
    }

}

Todos os nossos controllers terão o nome no formato NomePastaController e serão anotados com @Resource para expor seus recursos e tornar os métodos públicos acessíveis atavés da URL. No exemplo acima o caminho “/” irá executar o método index, tendo como regra o redirecionamento para uma página com o mesmo do método, contida dentro de uma pasta com o mesmo nome do controller WEB-INF/jsp/**index**/**index**.jsp. Todas as nossas páginas ficarão dentro de WEB-INF/jsp/pasta, onde pasta é o nome da entidade na qual queremos trabalhar.

@Path

O VRaptor é uma framework Action Based, logo os métodos são acessados via ações da URI, e estas devem ser únicas. A anotação Path indica a URI a ser acessada para que possamos executar um método. Por convenção o utilizado é o seguinte: @Path("nomeEntidade/nomeMetodo"):

@Path("/usuario/listarTodos")
public void listar() { }

Se não tivéssemos anotado o método listar com o @Path, por padrão seria /usuario/listar, mas podemos anotar e o Path não precisa ter relação com o nome do método.

Methods

O acesso a um método é feito através das operações HTTP mais comuns como o GET, POST, PUT e DELETE que normalmente são combinadas a um CRUD.

Podemos fazer um esqueminha do tipo: GET - Listar dados e acessar links; POST - Salvar uma entidade; PUT - Atualizar uma entidade; e DELETE - Excluir uma entidade.

Mesmo que um método tenha o mesmo Path ele pode se diferenciar através do método executado:

@Post
@Path("/usuario")
public void salvar() {
}

@Put
@Path("/usuario")
public void editar() {
}

Por padrão o acesso de um método é composto por nomeController/nomeMetodo via @Get, não sendo necessário neste caso anotar, porém recomendo sempre explicitar, evitando confudir o método de acesso assim como o caminho.

Criando a página inicial

Agora criaremos uma página chamada index.jsp dentro de uma pasta chamada index contendo um menu para a navegação:

...
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
...
<a href="<c:url value='/usuario/novo'/>">Novo usuário
...

Utilize sempre a tag URL da biblioteca core para que as URLs sejam montadas corretamente.

Pronto! Podemos testar nossa aplicação. Adicione o projeto ao servidor e verá que o nome estará vraptor-blank-project. Esse é o nome default e é através dele que iremos acessar a aplicação: http://localhost:8080/vraptor-blank-project/. Mas queremos usar outro nome, então irei mostrar como trocar, pois renomear a pasta do projeto apenas não irá resolver.

Trocando o nome de deploy e o context-root da aplicação:

  • Visualize os arquivos ocultos e dentro da pasta do projeto conterá uma outra chamada .settings;
  • Abra o arquivo org.eclipse.wst.common.component em um editor de texto;
  • Altere a tag “deploy-name” para alterar o nome visualmente ao escolher a aplicação para deploy;
  • Altere a propriedade “context-root” para alterar o contexto de acesso da aplicação;
  • Faça o deploy e verá as alterações, podendo escolher o nome que achar melhor, no caso escolhi “project”.

Criando a página de cadastro do usuario (novo.jsp)

Criaremos 1 Controller + 1 DAO + 1 Pasta contendo as páginas.

...
<form action="<c:url value='/usuario'/>" method="post">
    Nome: <input type="text" name="usuario.nome" value="${usuario.nome}"/><br/>
    Senha: <input type="text" name="usuario.senha" value="${usuario.senha}"/><br/>
    E-mail: <input type="text" name="usuario.email" value="${usuario.email}"/><br/>
    <input type="submit" value="Salvar"/>
</form>
...

A action do formulário acessa /usuario via POST. Através do parâmetro name, setamos os valores nos atributos do objeto usuario. Através do parâmetro value, recuperamos o objeto usuario e utilizamos seus atributos para popular os campos.

Criando o modelo do usuário

private Integer id;
private String nome;
private String senha;
private String email;

// Getters e Setters.

Lembre-se que atributo name="usuario.nome" acessa de fato o objeto Usuario e tanto o objeto quanto seus atributos devem existir.

Criando o controller do usuário

De acordo com a ação do nosso formulário, criaremos um método acessado pela URI /usuario via POST.

@Post
@Path("/usuario")
public void salvar() { }

Ainda falta o objeto no qual seto os valores dos campos do formulário. Diferente do useBean do JSP os valores são setados em um objeto presente no próprio controller, falicitando assim o uso do mesmo. Este objeto assim como qualquer outro que formos trabalhar devem estar presentes como argumento do método invocado, podendo ser mais do que um:

@Post
@Path("/usuario")
public void salvar(Usuario usuario) { }

Com o objeto alimentado a partir do formulário podemos persistí-lo.

Trabalhando com redirecionamento:

Ao tentarmos salvar um usuário teremos um erro 404, pois seremos redirecionados para uma página com o mesmo nome do método: WEB-INF/jsp/usuario/salvar.jsp, mas como queremos manter o nome do método, porém sermos redirecionados para a página listagem.jsp teremos de fazer o redirecionamento no final do método.

Temos dois tipos de redirecionamentos:

Results.logic()

Redirecionamento para um método qualquer dos controllers. Devemos passar a classe do controller que iremos utilizar:

result.use(Results.logic()).redirectTo(UsuarioController.class).listagem();

UsuarioController.class pode ser substituido por getClass() indicado a própria classe, ou qualquer outro Controller.class.

Results.page()

Redirecionamento para uma página. Indicamos o caminho da página na qual queremos ser redirecionados (redirect) ou encaminhados (forward):

result.use(Results.page()).forward("WEB-INF/jsp/listagem.jsp");

Para maiores informações sobre a diferença entre os dois tipos leia este artigos do Rafael Ponte. (:

Nas últimas versões do VRaptor é possível usar estes métodos de forma resumida:

result.redirectTo(this).listagem(); // Poderia ser um fowardTo

Veja que já é entendido que listagem é um método, logo por debaixo dos panos é usado o Results necessário. Não é mais preciso especificar se será usado logic ou page. Também foi simplificado o uso do getClass() para simplesmente this, representando o próprio controller.

Ainda existem vários outros tipos de Results, mas por hora é isso que precisamos saber.

Voltando ao código, então teríamos nosso controller assim:

@Resource
public class UsuarioController {

    private Result result;

    @Get
    @Path("/usuario/novo")
        public void novo() {
    }

    @Post
    @Path("/usuario")
    public void salvar(Usuario usuario) {
        // Método que salva.
        result.forwardTo("WEB-INF/jsp/usuario/listagem.jsp");
    }
}

Colocamos o caminho das páginas a partir da pasta WEB-INF.

Injeção de dependência

Certamente se formos utilizar o método salvar seria lançado um NullPointerException, pois o objeto result não esta instanciado. Uma grande facilidade que temos é a injeção de dependência na qual injetamos o objeto através do contrutor da classe e a framework trata de controlar o que é necessário para intanciá-lo.

public UsuarioController(Result result) {
    this.result = result;
}

Então agora já temos nosso result instanciado. Da mesma forma se quiséssemos trabalhar com outros controllers, por exemplo, apenas injetaríamos os mesmos.

Por boas práticas recomenda-se criar interfaces com as assinaturas de suas classes e na injeção passá-las em vez das classes concretas, falicitando deste modo os testes de unidades, além de ter uma interface externa de acesso para sua aplicação.

Criando o Dao

Precisamos da camada de persistência e esta camada deve ser anotada como @Component. Componentes são instâncias de classes utilizadas pela sua aplicação para executar tarefas ou armazenar estados em diferentes formas. O Dao é um exemplo clássico.

Para o nosso exemplo, apenas para facilitar didáticamente, iremos colocar a classe como escopo de sessão para que possamos manter o estado dos nossos dados e simular o banco. Os escopos existentes são:

RequestScoped: estará disponível apenas durante a requisição; ApplicationScoped: será apenas um por aplicação; SessionScoped: será o mesmo durante uma http session; e PrototypeScoped: instanciado sempre que requisitado.

@SessionScoped
@Component
public class UsuarioDao {

    private List<Usuario> usuarioList = new ArrayList<Usuario>();
    private Integer id = 1;

    public void salvar(Usuario usuario) {
        usuarioList.add(usuario);
        usuario.setId(id++);
    }

}

Listando os dados: (listagem.jsp)

Para podermos listar nossas informações salvas, devemos passar estes dados através do request que o VRaptor encapsula, assim como o response, tornando esta tarefa mais fácil. O Result será utilizado para incluir os dados da seguinte forma:

result.include("usuarioList", usuarioList);

Para recuperarmos a lista do request podemos utilizar EL da seguinte forma: ${usuarioList}.

...
<table>
    <thead>
        <tr>
      <th>Nome</th>
            <th>Senha</th>
     <th>E-mail</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach items="${usuarioList}" var="item">
            <tr>
                <td>${item.nome}</td>
                <td>${item.senha}</td>
                <td>${item.email}</td>
            </tr>
        </c:forEach>
    </tbody>
</table>
...

No caso acima nós que incluímos a lista no request, porém por padrão o retorno já é colocado no request. O nome do retorno é formado pelo tipo da lista caso tenha mais o nome “List”: public List listar(); seria capturado como ${usuarioList} e se for apenas um objeto basta capturar o nome começando em minísculo: public Usuario consultar(); seria capturado como ${usuario}.

Vamos adicionar o método que retorna a lista no Dao:

public List<Usuario> loadAll() {
    return usuarioList;
}

E também no Controller:

@Get
@Path("/usuario")
public List<Usuario> listagem() {
    return usuarioDao.loadAll();
}

No método acima estou usando o default e deixando ele colocar o usuarioList no resquest assim como fazer o redirecionamento para listagem.jsp.

Criando a lógica para editar

Vamos adicionar uma coluna na tabela da listagem de dados para conter um link para a edição:

...
<td>
    <form action="<c:url value='/usuario'/>" method="post">
        <input type='hidden' name='_method' value='put'/>
        <input type='hidden' name='usuario.id' value='${item.id}'/>
        <input type="submit" value="Editar"/>
    </form>
</td>
...

Como nosso método editar é acessado via PUT teremos de fazer o formulário utilizá-lo, já que só há o suporte como GET e POST hoje em dia. Para isso criamos um campo hidden (que não aparece na tela) sobrescrevendo o atributo _method com o valor PUT, atributo no qual indica qual método que o formulário executará. O atributo id do objeto Usuario também esta escondido servindo apenas para indicar qual usuário queremos editar.

No nosso controller teremos o seguinte método:

@Put
@Path("/usuario")
public void editar(Usuario usuario) {
    Usuario entity = usuarioDao.loadById(usuario);
    result.redirectTo(this).novo(entity);
}

Repare que após recuperarmos o usuário que queremos editar fazemos uma chamada ao método novo(), pois é ele que redireciona para nosso formulário, porém preciso levar o usuário consultado para a tela, logo passo ele para o método que foi refatorado para incluir o usuário no request:

@Get
@Path("/usuario/novo")
public void novo(Usuario usuario) {
    result.include("usuario", usuario);
}

No nosso Dao também iremos adicionar o método que consulta o usuário pelo ID:

public Usuario loadById(Usuario usuario) {
    Usuario usuarioDelete = null;

    for (Usuario item : usuarioList) {
        if (item.getId() == usuario.getId()) {
            usuarioDelete = item;
            break;
        }
    }

    removerItem(usuarioDelete); // *
    return usuarioDelete;
}
  • removerItem(usuarioDelete) foi utilizado aqui apenas por não usármos um banco de dado real e o editar ser na verdade um remover e logo em seguida um salvar.

Procuro o usuário com o ID passado e o guardo, logo em seguida removo ele da lista:

private void removerItem(Usuario usuarioDelete) {
    if (usuarioList.remove(usuarioDelete)) {
        id--;
    }
}

Criando a lógica de exclusão

O método para deletar segui o mesmo raciocínio do editar, só não tendo que retornar os dados para a tela. Vamos adicionar uma coluna na tabela da listagem de dados para conter um link para a remoção:

<td>
    <form action='<c:url value="/usuario"/>' method="post">
        <input type='hidden' name='_method' value='delete/>
        <input type='hidden' name='usuario.id' value='${item.id}'/>
        <input type="submit" value="Excluir"/>
    </form>
</td>

Vamos criar um método anotado com @Delete no controller:

@Delete
@Path("/usuario")
public void remover(Usuario usuario) {
    usuarioDao.remover(usuario);
    result.use(Results.logic()).redirectTo(getClass()).listagem();
}

E então repassaremos para o nosso Dao remover o objeto:

public void remover(Usuario usuario) {
    Usuario usuarioDelete = null;

    for (Usuario item : usuarioList) {
        if (item.getId() == usuario.getId()) {
            usuarioDelete = item;
            break;
        }
    }

    removerItem(usuarioDelete);
}

No método acima busco pelo ID, ao achar guardo o objeto e o removo da lista. Em uma situação real removeríamos o objeto do banco de dados de acordo com o ID passado.

Conclusão

De forma fácil conseguimos criar um CRUD sem nos preocupar com requisições, conversões de dados e redirecionamentos. Podemos fazer isso e muito mais com o VRaptor. E se esta preocupado com a parte visual é só dar uma olhada no jQuery UI.

Para quem quiser se aprofundar mais no VRaptor, acompanhe a lista de discussão, de uma olhada na documentação oficial disponível em português ou continue acompanhando os artigos aqui no Blog.

Link do projeto:

http://github.com/wbotelhos/iniciando-com-vraptor-3