Skip to content

3. Ações: A Resposta

Vamos considerar a arquitetura de uma aplicação Spring MVC:

Neste capítulo, examinamos o processo que encaminha a solicitação [1] para o controlador e a ação [2a] que a processará, um mecanismo conhecido como roteamento. Apresentamos também as várias respostas [3] que uma ação pode devolver ao navegador. Estas podem 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 Maven onde será colocada a saída da compilação do projeto;
  • em [5], o nome atribuído ao resultado da compilação;
  • em [6], uma descrição do projeto;
  • em [7], o pacote onde a classe executável do projeto será colocada;
  • em [8], a natureza do projeto. Trata-se de um projeto web com vistas Thymeleaf. Aqui, vemos todas as dependências Maven prontas a usar fornecidas pelo projeto Spring Boot;
  • em [9], especificamos que o resultado da compilação do Maven será empacotado num arquivo JAR em vez de um WAR. O projeto utilizará então um servidor Tomcat incorporado, que será incluído nas suas dependências;
  • em [10], avançamos para o próximo passo do assistente;
  • em [11], especifique o diretório do projeto;
  • em [12], o projeto gerado;
  • Em [14-15], renomeie o pacote [istia.st.springmvc];
  • em [16], o novo nome do pacote;
  • em [17], o novo projeto;

Criamos agora uma nova classe;

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

A classe está atualmente assim:


package istia.st.springmvc;
 
public class ActionsController {
 
}

Estamos a atualizar este código da seguinte forma:


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 tratam de URLs do cliente;
    • 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 devolvem o nome da vista que deve ser apresentada. É então a combinação desta vista e do modelo construído pela ação para esta vista que fornece a resposta enviada ao cliente.

A alteração na estrutura do nosso projeto requer uma alteração na configuração do nosso projeto:

  

A classe [Application] evolui da seguinte forma:


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] recebe como parâmetro uma matriz de nomes de pacotes onde o Spring Boot deve procurar componentes Spring. Aqui, incluímos o pacote [istia.st.springmvc.controllers] nesta matriz para que o controlador anotado com [@RestController] possa ser encontrado;

Iremos criar várias ações no controlador para ilustrar as suas principais funcionalidades. Primeiro, iremos concentrar-nos nos vários tipos de respostas possíveis para uma ação numa aplicação sem vistas.

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

A 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] especifica a solicitação tratada pela ação anotada:
    • o atributo [value] é a URL que está a ser processada,
    • o atributo [method] especifica o método aceite;

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

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

Vamos executar a aplicação como já fizemos várias vezes antes e, em seguida, utilizando o [Advanced Rest Client], solicitamos a URL [/a01] com um GET [1-2]:

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

Adicionamos a seguinte ação [/a02] ao [ActionsController] (por vezes, confundimos a URL com o método que a processa, referido como a ação):


    // ----------------------- accented characters - 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]. Este formato permite especificamente a utilização de caracteres acentuados;

Para aplicar esta nova ação, temos de reiniciar a aplicação:

 

O resultado é o seguinte:

  • em [1], vemos o tipo de documento enviado pelo servidor;
  • Em [2-3], os caracteres acentuados são claramente visíveis;

3.3. [/a03]: Devolver um fluxo XML

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


    // ----------------------- 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];

A sua execução produz o seguinte:

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

Lembre-se de que, no Chrome, pode visualizar as trocas HTTP entre o cliente e o servidor na consola do programador (Ctrl-Shift-I):

Image

A partir de agora, não iremos sistematicamente capturar imagens das trocas HTTP entre o cliente e o servidor. Por vezes, limitar-nos-emos a citar o texto dessas trocas.

3.4. [/a04, /a05]: devolução de um feed JSON

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


    // ----------------------- produce from 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. Recorde-se que, com um controlador [@RestController], o resultado da ação é a resposta enviada ao cliente. Como o HTTP é um protocolo para a troca de linhas de texto, a resposta do cliente deve ser serializada numa string. Para isso, o Spring MVC utiliza vários conversores [Object <---> string]. A associação de um objeto específico a um conversor é feita através da configuração. Aqui, a configuração automática do Spring Boot irá inspecionar as dependências do projeto:
 

As dependências do Jackson listadas acima são bibliotecas para serializar e deserializar objetos em cadeias de caracteres JSON. O Spring Boot utilizará então estas bibliotecas para serializar e deserializar os objetos devolvidos pelas ações. Um exemplo de código Java para serializar e deserializar objetos Java em JSON pode ser encontrado na Secção 9.7.

Repare que, na linha 2, não especificámos o tipo da resposta a ser enviada. Veremos qual o tipo predefinido que será enviado.

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

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


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

A classe [Person] é a seguinte:

  

package istia.st.sprinmvc.models;
 
public class Personne {
 
    // identifier
    private Integer id;
    // name
    private String nom;
    // age
    private int age;
 
    // manufacturers
    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 and setters
...
}

A execução produz os seguintes resultados:

  • em [1], o servidor indica que o documento que está a enviar é JSON;
  • em [2], o documento JSON recebido;

3.5. [/a06]: devolver um fluxo vazio

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


    // ----------------------- render an empty stream ------------------------
    @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 está a enviar um documento vazio.

3.6. [/a07, /a08, /a09]: tipo de dados 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] devolve um fluxo HTML [text/html];
  • linha 4: uma cadeia de caracteres HTML;

A execução produz os seguintes resultados:

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

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


    // ----------------------- result HTML in 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 indicou que estava a enviar um fluxo [text/plain] [2];

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


    // ----------------------- result HTML in 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 [text/xml];

Os resultados são os seguintes:

  • Em [1], o Chrome não interpretou a tag HTML <h1> porque o servidor indicou que estava a enviar um fluxo [text/xml] [2]. Em seguida, tratou a tag <h1> como uma tag XML;

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

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

Criamos um novo controlador [RedirectController]:

 

O código para o [RedirectController] 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 significa que, por predefinição, o tipo [String] do resultado da ação refere-se agora ao nome de uma ação ou de uma vista;

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


    // ------------ bridge to third-party action -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • Linha 4: Devolvemos «a01» como resultado, que é o nome de uma ação. Esta ação enviará então a resposta ao cliente;

Aqui está um exemplo:

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

Criamos agora a seguinte ação [/a11]:


    // ------------ temporary 302 redirect to a third-party action -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Obtemos os seguintes resultados:

  • Nos registos do Chrome [1-2], vemos duas solicitações, uma para [/a11] e outra para [/a01];
  • em [3], o servidor responde com um código de estado [302], que instrui o navegador do cliente a redirecionar para o URL especificado pelo cabeçalho HTTP [Location:] [4]. O código de estado [302] é um código de redirecionamento temporário;

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

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

Pode querer indicar um redirecionamento permanente; nesse caso, deve enviar o seguinte cabeçalho HTTP ao cliente:

HTTP/1.1 301 Moved Permanently 

o que significa que o redirecionamento é permanente. Esta diferença entre um redirecionamento temporário (302) e um redirecionamento permanente (301) é tida em conta por alguns motores de busca.

Escrevemos a ação [/a12] que irá realizar este redirecionamento permanente:


    // ------------ permanent 301 redirect to a third-party action----------------
    @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: definimos o [status] da resposta, o cabeçalho HTTP [301]:
HTTP/1.1 301 Moved Permanently
  • linha 5: criamos manualmente o seguinte cabeçalho HTTP:
Location: /a01 

que é o URL de redirecionamento.

A execução produz os seguintes resultados:

A partir deste exemplo, vamos aprender a:

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

3.8. [/a13]: gerar a resposta completa

É possível controlar totalmente a resposta, como demonstrado pela seguinte ação na classe [ResponsesController]:

  

    // ----------------------- complete response generation ------------------------
    @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]. Neste caso, para enviar uma resposta não vazia ao cliente, deve utilizar o objeto [HttpServletResponse response] fornecido pelo Spring MVC;
  • linha 4: atribuímos um estado à resposta que não será reconhecido pelo cliente;
  • linha 5: adicionamos um cabeçalho HTTP que não será reconhecido pelo cliente;
  • linha 6: adicionamos um cabeçalho HTTP [Content-Type] para especificar o tipo de dados que estamos a enviar, neste caso HTML;
  • Linhas 7–8: O documento que se segue aos cabeçalhos HTTP na resposta;

Os resultados são os seguintes:

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

Se o cliente não for um navegador, mas sim um cliente programático, pode utilizar os códigos de estado e cabeçalhos que desejar.