Skip to content

3. Ações: a resposta

Consideremos a arquitetura de uma aplicação Spring MVC:

Neste capítulo, analisamos o processo que leva a solicitação [1] ao controlador e à ação [2a] que irão processá-la, um mecanismo conhecido como roteamento. Além disso, apresentamos as diferentes respostas [3] que uma ação pode enviar ao navegador. Pode ser algo diferente de uma visualização V [4b].

3.1. O novo projeto

Criamos um novo projeto Spring MVC:

  • em [1-2], criamos um novo projeto baseado no Spring Boot;
  • em [3], o nome do projeto Maven;
  • em [4], o grupo do Maven no qual será colocado o resultado da compilação do projeto;
  • em [5], o nome dado ao produto da compilação;
  • em [6], uma descrição do projeto;
  • em [7], o pacote no qual será colocada a classe executável do projeto;
  • em [8], a natureza do projeto. Trata-se de um projeto web com visualizações Thymeleaf. Observa-se aqui todas as dependências do Maven prontas para uso oferecidas pelo projeto Spring Boot;
  • em [9], indica-se que o produto resultante da compilação do Maven será empacotado em um arquivo jar e não war. O projeto utilizará, então, um servidor Tomcat embutido que estará entre suas dependências;
  • em [10], seguimos adiante no assistente;
  • em [11], especifica-se a pasta do projeto;
  • em [12], o projeto gerado;
  • em [14-15], renomeia-se o pacote [istia.st.springmvc];
  • em [16], o novo nome do pacote;
  • em [17], o novo projeto;

Agora criamos uma nova classe;

  • em [1-3], criamos uma nova classe;
  • em [5], atribuímos a ela e, em [4], especificamos seu pacote;
  • em [6], o novo projeto;

A classe, por enquanto, é a seguinte:


package istia.st.springmvc;

public class ActionsController {

}

Evoluímos esse código da seguinte maneira:


package istia.st.springmvc;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class ActionsController {

}
  • linha 6: a anotação [@RestController] indica duas coisas:
    • que a classe [ActionsController], assim anotada, é um controlador Spring MVC e, portanto, contém ações que processam os URL dos clientes;
    • que o resultado dessas ações é enviado ao cliente;

A outra anotação [@Controller] que encontramos é diferente: as ações de um controlador assim anotado retornam o nome da visualização que deve ser exibida. É, então, a combinação dessa visualização com o modelo construído pela ação para essa visualização que fornece a resposta enviada ao cliente.

A mudança na estrutura do nosso projeto implica uma alteração na configuração do projeto:

  

A classe [Application] sofre as seguintes alterações:


package istia.st.springmvc.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • linha 9: a anotação [ComponentScan] aceita como parâmetro uma matriz de nomes de pacotes onde o Spring Boot deve procurar componentes Spring. Aqui, colocamos nessa matriz o pacote [istia.st.springmvc.controllers] para que o controlador anotado por [@RestController] seja encontrado;

Vamos criar várias ações no controlador para ilustrar suas principais características. Primeiramente, vamos nos concentrar nos diversos tipos de respostas possíveis de uma ação em um aplicativo sem visualizações.

3.2. [/a01, /a02] - Hello world

Nossa primeira ação será a seguinte:


@RestController
public class ActionsController {
    // ----------------------- hello world ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • linha 4: a anotação [RequestMapping] qualifica a solicitação processada pela ação anotada:
    • o atributo [value] é a solicitação URL processada,
    • o atributo [method] define o método aceito;

Assim, o método [a01] processa a solicitação HTTP [GET /a01].

  • linha 5: o método [a01] retorna um tipo [String], que será enviado tal como está ao cliente;
  • linha 6: a string retornada;

Vamos executar o aplicativo como já fizemos várias vezes e, em seguida, com o cliente [Advanced Rest Client], solicitamos o URL [/a01] com um GET [1-2]:

  • em [3], a resposta do servidor;
  • em [4], os cabeçalhos HTTP da resposta. Vemos que a codificação utilizada é [ISO-8859-1]. Podemos preferir a codificação UTF-8. Isso pode ser configurado;
  • em [5], solicitamos o mesmo URL com o navegador Chrome;

Adicionamos a seguinte ação [/a02] no controlador [ActionsController] (assim, às vezes confundiremos o URL com o método que o processa sob o nome de ação):


    // ----------------------- caracteres acentuados - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • linha 2: o atributo [produces="text/plain;charset=UTF-8"] indica que a ação envia um fluxo de texto com caracteres codificados no formato [UTF-8]. Esse formato permite, entre outras coisas, o uso de caracteres acentuados;

Para que essa nova ação seja reconhecida, precisamos reiniciar o aplicativo:

 

O resultado é o seguinte:

  • em [1], vemos a natureza do documento enviado pelo servidor;
  • em [2-3], os caracteres acentuados aparecem corretamente;

3.3. [/a03]: gerar um fluxo XML

Adicionamos a ação [/a03] a seguir:


    // ----------------------- text/xml ------------------------
    @RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a03() {
        String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
        return greeting;
}
  • linha 2: o atributo [produces="text/xml;charset=UTF-8"] indica que a ação envia um fluxo XML com caracteres codificados no formato [UTF-8];

Sua execução resulta no seguinte:

  • em [1], o cabeçalho HTTP especifica que o documento enviado é do tipo HTML;
  • em [2], o navegador Chrome usa essa informação para formatar o texto XML recebido;

Vale lembrar que, no Chrome, é possível acessar as trocas de dados entre o cliente e o servidor na janela de desenvolvimento (Ctrl+Shift+I):

Image

A partir de agora, não faremos capturas de tela sistemáticas das trocas HTTP entre o cliente e o servidor. Às vezes, nos limitaremos a indicar o texto dessas trocas.

3.4. [/a04, /a05]: gerar um fluxo jSON

Adicionamos a seguinte ação [/a04]:


    // ----------------------- gerar jSON ------------------------
    @RequestMapping(value = "/a04", method = RequestMethod.GET)
    public Map<String, Object> a04() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "un");
        map.put("2", new int[] { 4, 5 });
        return map;
}
  • linha 3: a ação retorna um tipo [Map], um dicionário. Lembramos que, com um controlador do tipo [@RestController], o resultado da ação é a resposta enviada ao cliente. Como o protocolo HTTP é um protocolo de troca de linhas de texto, a resposta do cliente deve ser serializada em uma cadeia de caracteres. Para isso, o Spring MVC utiliza diversos conversores [Objet <---> chaîne de caractères]. A associação de um objeto específico a um conversor é feita por meio de configuração. Aqui, a autoconfiguração do Spring Boot irá inspecionar as dependências do projeto:
 

As dependências do Jackson acima são bibliotecas de serialização/desserialização de objetos em cadeias jSON. O Spring Boot utilizará, então, essas bibliotecas para serializar/desserializar os objetos retornados pelas ações. Um exemplo de código Java para serializar/deserializar objetos Java em jSON pode ser encontrado no parágrafo 9.7.

Observe-se, na linha 2, que não especificamos o tipo da resposta enviada. Veremos qual será o tipo padrão que será enviado.

Os resultados no Chrome [1-3] são os seguintes:

Vamos agora adicionar a seguinte ação [/a05]:


    // ----------------------- gerar jSON - 2 ------------------------
    @RequestMapping(value = "/a05", method = RequestMethod.GET)
    public Personne a05() {
        return new Personne(1,"carole",45);
}

A classe [Personne] é a seguinte:

  

package istia.st.sprinmvc.models;

public class Personne {

    // identificador
    private Integer id;
    // nome
    private String nom;
    // idade
    private int age;

    // construtores
    public Personne() {

    }

    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }

    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }

    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

    // getters e setters
...
}

A execução produz os seguintes resultados:

  • em [1], o servidor indica que o documento que está enviando é o jSON;
  • em [2], o documento jSON recebido;

3.5. [/a06]: retornar um fluxo vazio

Adicionamos a seguinte ação [/a06]:


    // ----------------------- retornar um fluxo vazio ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • na linha 3, a ação [/a06] não retorna nada. O Spring MVC irá, então, gerar uma resposta vazia para o cliente;

A execução produz os seguintes resultados:

 

Acima, o atributo HTTP [Content-Length] na resposta indica que o servidor envia um documento vazio.

3.6. [/a07, /a08, /a09]: natureza do fluxo com [Content-Type]

Adicionamos a seguinte ação [/a07]:


    // ----------------------- text/html ------------------------
    @RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
    public String a07() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • linha 2, a ação [/a07] gera um fluxo HTML [text/html];
  • linha 4: uma sequência HTML;

A execução produz os seguintes resultados:

  • em [1], vemos que o Chrome interpretou a tag HTML <h1>, que exibe seu conteúdo em letras grandes;

Agora, vamos fazer o mesmo com a seguinte ação [/a08]:


    // ----------------------- resultado de HTML em text/plain ------------------------
    @RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String a08() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • linha 2: a resposta da ação é do tipo [text/plain];

Os resultados são os seguintes:

  • em [1], o Chrome não interpretou a tag HTML <h1> porque o servidor informou que estava enviando um fluxo [text/plain] [2];

Vamos tentar novamente algo semelhante com a ação [/a09] a seguir:


    // ----------------------- resultado de HTML em text/xml ------------------------
    @RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a09() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • linha 2: enviamos um fluxo do tipo [text/xml];

Os resultados são os seguintes:

  • no [1], o Chrome não interpretou a tag HTML <h1> porque o servidor informou que estava enviando um fluxo [text/xml] [2]. Ele, então, tratou a tag <h1> como uma tag XML;

Esses exemplos destacam a importância do cabeçalho HTTP [Content-Type] na resposta do servidor. O navegador utiliza esse cabeçalho para saber como interpretar o documento que recebe;

3.7. [/a10, /a11, /a12]: redirecionar o cliente

Criamos um novo controlador [RedirectController]:

 

O código de [RedirectCntroller] será, por enquanto, o seguinte:


package istia.st.springmvc.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class RedirectController {
}
  • linha 7: utilizamos a anotação [@Controller], o que faz com que, a partir de agora, por padrão, o tipo [String] do resultado das ações indique o nome de uma ação ou de uma visualização;

Criamos a seguinte ação [/a10]:


    // ------------ redirecionamento para uma ação de terceiros -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • linha 4: retornamos como resultado 'a01', que é o nome de uma ação. Será ela, então, que enviará a resposta ao cliente;

Veja um exemplo:

  • em [2], recebemos o fluxo da ação [/a01];
  • no [3], o navegador exibe o URL da ação [/a10];

Agora, criamos a seguinte ação [/a11]:


    // ------------ redirecionamento temporário 302 para uma ação de terceiros -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Obtemos os seguintes resultados:

  • nos logs do Chrome [1-2], vemos duas solicitações: uma para [/a11] e outra para [/a01];
  • em [3], o servidor responde com um código [302] que solicita que o navegador cliente seja redirecionado para oURL indicado pelo cabeçalho HTTP [Location:] [4]. O código [302] é um código de redirecionamento temporário;

O navegador então faz a segunda solicitação para o código de redirecionamento URL:

  • em [5], a segunda solicitação do cliente;
  • para [6], o navegador do cliente exibe o URL da solicitação de redirecionamento;

Pode-se querer indicar um redirecionamento permanente; nesse caso, é necessário enviar ao cliente o cabeçalho HTTP a seguir:

HTTP/1.1 301 Moved Permanently 

o que significa que o redirecionamento é permanente. Essa diferença entre redirecionamento temporário (302) e permanente (301) é levada em consideração por alguns mecanismos de busca.

Escrevemos a ação [/a12], que realizará esse redirecionamento permanente:


    // ------------ redirecionamento permanente 301 para uma ação de terceiros----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • linha 3: solicitamos ao Spring MVC que injete o objeto [HttpServletResponse], que encapsula a resposta enviada ao cliente;
  • linha 4: define-se o [status] da resposta, o [301] do cabeçalho HTTP:
HTTP/1.1 301 Moved Permanently
  • linha 5: cria-se manualmente o cabeçalho HTTP a seguir:
Location: /a01 

que é o cabeçalho de redirecionamento URL.

A execução produz os seguintes resultados:

Deste exemplo, destaca-se a maneira de:

  • gerar o status da resposta HTTP;
  • incluir um cabeçalho HTTP na resposta;

3.8. [/a13]: gerar a resposta completa

É possível controlar totalmente a resposta, conforme mostra a ação a seguir da classe [ResponsesController]:

  

    // ----------------------- geração completa da resposta ------------------------
    @RequestMapping(value = "/a13")
    public void a13(HttpServletResponse response) throws IOException {
        response.setStatus(666);
        response.addHeader("header1", "qq chose");
        response.addHeader("Content-Type", "text/html;charset=UTF-8");
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        response.getWriter().write(greeting);
}
  • linha 3: o resultado da ação é [void]. Nesse caso, para enviar uma resposta não vazia ao cliente, é preciso usar o objeto [HttpServletResponse response] fornecido pelo Spring MVC;
  • linha 4: atribui-se à resposta um status que não será reconhecido pelo cliente;
  • linha 5: adiciona-se um cabeçalho HTTP que não será reconhecido pelo cliente;
  • linha 6: adiciona-se um cabeçalho HTTP [Content-Type] para especificar o tipo de fluxo que será enviado, neste caso, HTML;
  • linhas 7-8: o documento que seguirá os cabeçalhos HTTP na resposta;

Os resultados são os seguintes:

  • em [1], reconhecemos os elementos da nossa resposta;
  • em [2-3], percebe-se que o Chrome ignorou o fato de que:
    • o status HTTP da resposta não era um status HTTP reconhecido,
    • que o cabeçalho [header1] não era um cabeçalho HTTP reconhecido;

Se o cliente não for um navegador, mas um cliente programado, é possível usar os status e cabeçalhos que se desejar.