Comunicação view/controller via POST com Spring MVC

Este artigo aborda a comunicação entre uma view e um controller em uma aplicação Spring via post e contém a base para se trabalhar com formulários nesse framework.

Fique por dentro
Formulários são uma forma fundamental de interação entre um usuário e a aplicação, sendo o seu domínio imprescindível para qualquer desenvolvedor web. Aqui falamos sobre como trabalhar com eles em aplicações Spring, utilizando para isso o Spring MVC e o Thymeleaf.

Nas aulas de HTML aprendemos que o navegador dispõe de mecanismos para que as informações preenchidas em um formulário sejam submetidas para um servidor. Enquanto isso, na programação com Java vemos que uma aplicação é composta por objetos que possuem propriedades em forma de getters/setters e que interagem entre si através desses e de outros métodos. Então, como conectar esses dois mundos? Respondemos a essa pergunta aqui com um exemplo prático de envio de formulário de cadastro.

Introdução

Nos artigos anteriores da matéria de Spring aprendemos como o Spring MVC consegue conectar as propriedades de um objeto aos inputs de um formulário através do Thymeleaf.

Com isso, quando o formulário é enviado recebemos no back-end um objeto que representa os dados fornecidos pelo usuário e que pode ser utilizado pela aplicação. Aqui veremos o que muda no controller e na view quando esse envio é feito via POST.

Relembrando o que aprendemos, em uma aplicação web Spring o Spring MVC está mais próximo do Java, fornecendo meios para escrevermos classes que representam os componentes do MVC, como models e controllers, enquanto o Thymeleaf consiste de uma linguagem de templates com a qual escrevemos as views da aplicação.

Assim, utilizando atributos do Thymeleaf em elementos, em lugar daqueles nativos do HTML, conectamos o front-end e back-end da aplicação.

Isso é algo semelhante ao que aprendemos nas aulas de HTML, onde criamos/enviamos formulários utilizando mecanismos já existentes. Contudo, aqui faremos isso sabendo que tais mecanismos estarão sob o controle do framework.

Na prática, cabe a nós entendermos quais dados um formulário pretende capturar, modelando cada um deles em um objeto.

A partir disso, usamos o Spring para expor os getters/setters desse objeto para a view, como vemos na Figura 1. Então, na view utilizamos o Thymeleaf sobre o HTML a fim de relacionar tais propriedades do objeto com os inputs no formulário. Com isso, quando ele for enviado um objeto devidamente preenchido com os dados fornecidos pelo usuário será entregue para o back-end

Figura 1. Conexão entre getters/setters e inputs

Neste artigo veremos esses três passos (criação de um formulário com o Thymeleaf, recebimento dos dados em um controller e apresentação deles em uma view) usando como cenário um formulário de cadastro.

Conhecendo o formulário de exemplo

Para estudar esses recursos do Spring MVC e do Thymeleaf usaremos como exemplo um formulário de cadastro.

A partir dele receberemos de um usuário o seu nome, data de nascimento, apelido, senha e a confirmação de concordância com os termos de uso. Com isso, será possível explorar como transformar inputs date em objetos Date, checkboxes em booleans e text em Strings.

Para isso precisaremos de duas views. A primeira (Figura 2) será utilizada como home, apresentando para o usuário o formulário de cadastro.

Ao serem enviados, os dados informados pelo usuário serão recebidos no controller da aplicação que fará a apresentação deles em uma segunda view (Figura 3).

Figura 2. Primeira view - Formulário de cadastro
Figura 3. Segundo view - Dados enviados pelo usuário

Com isso aprenderemos:

  1. A criar uma view de formulário;
  2. Como receber os dados preenchidos pelo usuário no controller;
  3. E enviar os dados recebidos pelo controller para a segunda view.

Visto que este fluxo é o tema principal deste artigo, outros assuntos como validação de dados, por exemplo, serão abordados em um outro momento.

Estrutura do projeto

Usaremos neste exemplo o padrão arquitetural MVC, que divide a aplicação em modelo, visão e controle.

Dentro do Spring MVC, fazem parte do modelo as classes que criamos para representar os dados que serão processados pelas regras de negócio.

Na camada de controle estão os controllers - seu principal papel é receber e encaminhar as requisições, conectando assim as camadas de modelo e view.

As views, por sua vez, são os templates que criamos com o Thymeleaf e que dão origem às páginas web da aplicação.

Do ponto de vista do MVC nossa aplicação estará dividida conforme a Figura 4.

Figura 4. Divisão dos componentes da aplicação

Configuração do projeto

Como de costume, usaremos o Maven como gerenciador de projeto aqui e, conforme esperado, será no arquivo pom.xml que adicionaremos os dados de versão, dependências, bem como a configuração de construção e execução da aplicação. Esse conteúdo é apresentado no Código 1.

<?xml version="1.0" encoding="UTF-8"?> <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>org.example</groupId> <artifactId>springboot_post</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <properties> <java.version>13</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Código 1. Arquivo pom.xml

Caso você já tenha lido o artigo anterior a este, Spring MVC: Entendendo a comunicação view/controller via GET, verá que esse arquivo possui poucas novidades em relação ao exemplo anterior.

Trata-se de uma aplicação Spring Boot que utiliza os starters spring-boot-starter-web para adicionar suporte ao Spring Framework, que contém o Spring MVC, assim como o spring-boot-starter-thymeleaf, que configura o Thymeleaf como motor de templates integrado ao Spring MVC.

As demais configurações informam a versão da linguagem e quais plugins devem ser utilizados para colocá-la em execução, nesse caso o spring-boot-maven-plugin e maven-compiler-plugin.

User.class

A primeira classe que criaremos será User.class (Código 2), que faz parte da camada modelo da aplicação. Nela modelamos os dados que representam nosso contexto de domínio, neste caso um usuário do sistema (User).

public class User { private String name; private Date birthDate; private String nickname; private String password; private boolean according; public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isAccording() { return according; } public void setAccording(boolean according) { this.according = according; } }
Código 2. Classe User

Mais adiante veremos que o formulário de cadastro conterá um input para cada getter/setter da classe User (Figura 5) e veremos também como fazer essa conexão. Então, assim como temos aqui o atributo name e as propriedades getName() e setName(), teremos no formulário um input/text para receber o valor desse campo utilizando getName() e colocar valor nele invocando setName().

Da mesma forma, teremos um input/date para receber/atribuir o valor de birthDate, um checkbox para according e assim por diante. Isso será feito automaticamente pelo framework e ficará mais claro ao observar o código do formulário.

Figura 5. Conexão entre as propriedades da classe e os inputs do formulário

UserController.class

No MVC uma das responsabilidades dos controllers é expor recursos, o que pode ser qualquer coisa no servidor, como imagens, PDFs, registros em um banco de dados etc.

Geralmente, por uma questão de organização e boas práticas, cada recurso possui o seu respectivo controller. Neste exemplo, o recurso em questão são os dados do usuário. Com a classe que o representa pronta (User.class), podemos passar para a construção do seu controller.

Conforme aprendemos nos artigos anteriores desta matéria, no Spring Framework as requisições são recebidas por controllers.

Em uma aplicação real é uma boa prática que cada controller lide com apenas um recurso. Dessa forma, visto que o recurso em questão são os dados de cadastro de uma pessoa, criaremos um controller chamado UserController para esse exemplo.

Essa classe terá dois métodos:

  1. Um método para devolver a Home do nosso exemplo, ou seja, a página que apresenta o formulário vazio para que o usuário possa preencher os dados. Esta requisição será recebida via GET, pois o controller apenas recebe o pedido de exibição do formulário.
  2. O segundo método é o que recebe os dados digitados pelo usuário e em seguida devolve esses dados para confirmação na segunda view do exemplo (veja imagem abaixo). Neste caso como o controller está recebendo informações digitadas pelo usuário, o ideal é utilizarmos o verbo POST - dessa forma os dados informados não ficarão expostos na URL do browser.
Slide 1. Sequência de requisição/resposta da aplicação

Vamos escrever a classe de controller. Em sua IDE, crie uma classe chamada UserController no pacote br.com.devmedia.springmvc.form.controller. Nela insira o Código 3, que vemos abaixo. A explicação deste código vem logo a seguir.

@Controller @RequestMapping("/users") public class UserController { @GetMapping public String usersForm(final Model model) { model.addAttribute("user", new User()); return "userForm"; } @PostMapping public String result(@ModelAttribute User user) { return "result"; } }
Código 3. Classe UserController

Toda classe controller deve receber pelo menos a anotação @Controller, dessa forma ela pode ser identificada pelo framework como um componente que desempenha esse papel na aplicação.

Cada controller responde a uma URL e cada URL pertence a um recurso, que por sua vez pode ser qualquer coisa acessível no servidor, desde dados de cadastro, arquivos etc.

Para determinar a qual URL o controller responderá usamos a anotação @RequestMapping. Neste exemplo, a URL passada para essa anotação foi /users e por isso toda vez que acessamos a URL http://localhost:8080/users estaremos disparando uma requisição para UserController.

8080 é a porta padrão do servidor web utilizado neste exemplo, o qual foi configurado automaticamente no pom.xml.

Podemos notar que a classe UserController possui dois métodos:

  1. usersForm - devolve a ‘home’ da aplicação, ou seja, o formulário vazio.
  2. result - recebe os dados digitados pelo usuário e devolve a segunda view para o usuário confirmar os dados que digitou.

Métodos do Controller e verbos HTTP

Por padrão, @RequestMapping atende a todos os verbos HTTP, inclusive GET e POST.

Para direcionar um verbo específico para um método do controller usamos outras anotações que possuem em seu nome o verbo desejado.

No exemplo o método usersForm é anotado com @GetMapping, para servir a página do formulário através do método GET, enquanto o método result é anotado com @PostMapping, o que lhe permite receber os dados do formulário via método POST.

As strings retornadas por esses métodos, usersForm e result, são nomes de views que correspondem aos documentos userForm.html e result.html, que veremos em breve.

A magia do Spring

Os dados enviados e recebidos através do protocolo HTTP são, por padrão, texto. Isso gera um problema de infraestrutura porque o back-end lida com objetos.

Aqui entra o Spring, fazendo essa conversão automaticamente (Figura 6).

Figura 6. Conexão entre o formulário o objeto

O framework faz isso utilizando classes conversoras, mas o funcionamento delas não é importante agora. Afinal, o trabalho de qualquer framework é esconder a complexidade, gerando produtividade.

Vamos entender qual é a contribuição do controller para que essa magia aconteça :)

Primeiro passo: conectando um objeto vazio ao formulário

A conexão entre a classe de modelo e o formulário começa no controller, com as linhas do Código 4.

public String usersForm(final Model model) { model.addAttribute("user", new User());
Código 4. Conexão entre a classe de modelo e o formulário

Na arquitetura do Spring o primeiro passo é passar como parâmetro do método a classe Model. O papel dela é armazenar os dados que queremos expor para a view.

Em seguida, utilizamos o método addAttribute para adicionar em Model uma instância da classe User. Com isso a view terá acesso a um objeto vazio para ser preenchido com os dados que o usuário informar no formulário.

Na parte do controller é isso. Adiante usaremos o Thymeleaf na camada de view para que o processo de integração model → view fique completo.

Segundo passo: conectando o método result

O recebimento dos dados digitados em forma de objeto é ainda mais simples, pois basta seguir a sintaxe apresentada na linha do Código 5.

public String result(@ModelAttribute User user) {
Código 5. Recebendo os dados digitados

Ao anotar o parâmetro do método com @ModelAttribute estamos indicando que os dados devem ser convertidos para um objeto da classe User.

userForm.html

Com o back-end pronto podemos passar para o front-end. Vamos começar criando a view de formulário.

No Código 6 vemos o template Thymeleaf desta página. Ele contém, além de HTML, os atributos th:action, th:object e th:field do Thymeleaf. Vamos nos ater a eles pois é onde a conexão entre back-end e front-end se conclui.

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Faça o seu cadastro</title> </head> <body> <h1>Faça o seu cadastro</h1> <form th:action="@{/users}" th:object="$" method="post"> <div> <label th:for="name">Nome:</label> <input type="text" th:field="*" placeholder="Seu Nome" required="required"> </div> <div> <label th:for="birthDate">Data de nascimento:</label> <input type="date" th:field="*" required="required"> </div> <div> <label th:for="nickname">Apelido:</label> <input type="text" th:field="*" placeholder="apelido" required="required"> </div> <div> <label th:for="password">Senha:</label> <input type="password" th:field="*" placeholder="******" minlength="6" required="required"> </div> <div> <label>Concordo com os termos de serviço</label> <input type="checkbox" th:field="*" required="required"> </div> <div> <button type="submit">Enviar</button> </div> </form> </body> </html>
Código 6. Código da view userForm.html

Entendendo th:action

Na construção de um formulário com o Thymeleaf não utilizamos a tag action para determinar o destino dos dados, como faríamos no HTML, mas th:action em seu lugar. Para informar a URL usamos uma expressão do Thymeleaf, nesse caso @{/users}, com arroba antes das chaves, como mostra o Código 7.

O Thymeleaf completa a URL expression com o endereço da aplicação, que nesse caso é http://localhost:8080. Com isso @{/users} se torna http://localhost:8080/users.
<form th:action="@{/users}" th:object="$" method="post">
Código 7. Exemplo extraído de userForm.html

Entendendo th:object

Para informar ao Thymeleaf qual objeto ele deve utilizar no formulário usamos a atributo th:object no elemento form. O valor desse atributo deve usar a sintaxe ${ }, com o dólar antes das chaves, como vemos no Código 8.

${ } se chama expressão de variável.
<form th:action="@{/users}" th:object="$" method="post">
Código 8. Exemplo extraído de userForm.html

De onde vem o objeto que utilizamos como valor para th:object?

Esse objeto é criado no método usersForm do controller e adicionado como atributo a uma instância de model. Lembre-se que Model é a classe utilizada para transmitir dados do controller para a view e, portanto, os dados adicionados nela podem ser utilizados pelo Thymeleaf, como vemos no Código 9.

@RequestMapping("/users") public class UserController { ... @GetMapping public String usersForm(final Model model) { model.addAttribute("user", new User()); return "userForm"; } … }
Código 9. UserController.class

A propriedade th:object possui duas diretrizes:

  1. Apenas nomes de objetos podem ser utilizados. Por exemplo, o valor ${users.birthDate} está errado, pois contém tanto o nome quanto um campo de um objeto, mas $ está correto pois contém apenas o nome dele.
  2. Não podemos usar um th:object dentro de um form que já utilize esse atributo. Essa é uma forma do Thymeleaf se manter coerente em relação ao HTML, que impede elementos form de serem aninhados.

Agora que o formulário possui um objeto, o próximo passo será utilizar as propriedades dele nos inputs.

Entendendo th:field

Enquanto th:object é utilizado para conectar formulário e objeto, th:field conecta um input com um getters/setter desse objeto.

Portanto, para cada propriedade do objeto teremos um input no formulário utilizando th:field, cujo valor será o nome da propriedade do objeto ao qual desejamos conectar o input.

A sintaxe de th:field é *{ }, utilizando asterisco antes das chaves, como vemos no Código 10.

<input type="text" th:field="*" placeholder="Seu Nome" required="required">
Código 10. Exemplo extraído de userForm.html
*{ } se chama expressão de seleção.

Ao contrário de th:object, th:field suporta a navegação por propriedades do objeto. Dessa forma, dado um objeto Date, podemos acessar métodos como getTime() neste tipo de expressão.

Observe que th:field conecta o input com propriedades do objeto, o que significa que tanto getters quanto setters são utilizados aqui. Dessa forma, caso o objeto passado para o formulário não esteja vazio, os valores de seus campos serão apresentados nos inputs.

Isso é útil por exemplo quando queremos iniciar o formulário com valores padrão, ou então quando precisamos devolver o formulário para o usuário digitar alguma informação inválida (uma senha já existente etc.).

Nesse momento, automaticamente o Spring Conversion Service fará qualquer conversão de dados necessária, apresentando, por exemplo, datas formatadas se o campo em questão for uma instância de Date.

result.html

A view result (Código 11) é responsável por apresentar os dados que o usuário informou.

Após o usuário enviar o formulário, o método result anotado com @PostMapping em UserController será invocado, retornando essa view em resposta.

@PostMapping public String result(@ModelAttribute final User user) { return "result"; }
Código 11. Exemplo extraído da classe UserController

No documento result.html (Código 12) temos o template Thymeleaf dessa view. Parte dessa sintaxe já conhecemos no artigo Spring MVC: Entendendo a comunicação view/controller via GET, então aqui vamos nos ater ao que há de novo.

<html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Pessoa - Cadastro</title> </head> <body> <h1>Dados enviados pelo usuário:</h1> <p>Nome: <b th:text="${user.name}"/></p> <p>Data de nascimento: <b th:text="${{user.birthDate}}"/></p> <p>Apelido: <b th:text="${user.nickname}"/></p> <p>Senha: <b th:text="${user.password}"/></p> <p>Concorda com os termos de uso? <b th:text="${user.according}"/></p> </body> </html>
Código 12. Código da view result

O que observamos no Código 12 é a exposição de um objeto.

Usamos o atributo th:text do Thymeleaf para que o valor de um elemento venha de um getter do objeto. Com isso devemos usar a sintaxe ${ } ao definir o valor de th:text, caso contrário haverá um erro no processamento do template.

De onde vem o objeto que estamos utilizando aqui?

Ele vem do parâmetro anotado com @ModelAttribute no método result(@ModelAttribute User users) de UserController.

Dada a utilização de @ModelAttribute não precisamos passar os valores de cada campo do formulário para o objeto manualmente. Além disso, também não precisamos, neste caso, de um objeto Model para transmitir esses dados para a view, pois isso é feito automaticamente pelo framework.

Isso é importante: apenas um controller anotado com @RequestMapping pode utilizar classes da aplicação, como User, anotadas com @ModelAttribute. Então, não se esqueça de adicionar @RequestMapping ao controller para que esse exemplo funcione corretamente.

Executando a aplicação

A fim de tornar a aplicação executável precisamos criar uma última classe (Código 13) anotada com @SpringBootApplication.

@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Código 13. Classe executável da aplicação

Neste pequeno fragmento de código há muito significado, então vamos entendê-lo melhor.

No que se refere a sintaxe do Java, Application é uma classe executável como qualquer outra, dada a presença do método main. Para o Spring, é partir do método SpringApplication.run() que a aplicação será iniciada.

Acima da classe temos a anotação @SpringBootApplication que é responsável por dispor diversas funcionalidades, sem que para isso tenhamos que fazer qualquer configuração.

No contexto deste exemplo é importante saber que uma delas é a habilidade de buscar por outros componentes no pacote br.com.devmedia.springmvc.form, incluindo aqueles abaixo desse. É assim que o Spring consegue encontrar o controller UserController, um exemplo de componente que pode existir em uma aplicação.

Por que importa saber disso? Por hora, porque a classe executável da aplicação deve estar em um pacote acima dos demais ou os componentes presentes nos pacotes mais abaixo não serão encontrados pelo framework. Como resultado, ao digitar http://localhost:8080/users será retornado erro 404, uma vez que UserController não será localizado e executado em resposta a essa requisição.

Estando tudo pronto, basta executar a classe Application para que a aplicação seja iniciada e possa ser utilizada.

Conclusão

Com isso aprendemos como criar formulários com o Thymeleaf e enviá-los para um controller, exibindo os dados recebidos em uma view.

Artigos relacionados