Skip to content

16. Introdução ao Spring MVC

16.1. O papel do Spring MVC em uma aplicação web

Vamos contextualizar o Spring MVC no desenvolvimento de uma aplicação web. Na maioria das vezes, ela será construída sobre uma arquitetura multicamadas como a seguinte:

  • a camada [Web] é a camada que interage diretamente com o usuário da aplicação web. O usuário interage com a aplicação web por meio de páginas da web visualizadas em um navegador. É nessa camada que se situa o Spring MVC e exclusivamente nessa camada;
  • a camada [métier] implementa as regras de gestão da aplicação, tais como o cálculo de um salário ou de uma fatura. Essa camada utiliza dados provenientes do usuário por meio da camada [Web] e da camada SGBD por meio da camada [DAO];
  • A camada [DAO] (Data Access Objects), a camada [ORM] (Object Relational Mapper) e o driver JDBC gerenciam o acesso aos dados do SGBD. A camada [ORM] faz a ponte entre os objetos manipulados pela camada [DAO] e as linhas e colunas das tabelas de um banco de dados relacional. Uma especificação chamada JPA (Java Persistence API) permite abstrair-se do ORM utilizado, caso este implemente essas especificações. Esse será o caso neste tutorial e, portanto, passaremos a chamar a camada ORM de camada JPA;
  • a integração das camadas é feita pelo framework Spring;

16.2. O modelo de desenvolvimento do Spring MVC

O Spring MVC implementa o modelo de arquitetura denominado MVC (Modelo – Visão – Controlador) da seguinte maneira:

O processamento de uma solicitação de um cliente ocorre da seguinte maneira:

  1. solicitação — as solicitações URL têm o formato http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... O [Front Controller] utiliza um arquivo de configuração ou anotações Java para “rotear” a solicitação para o controlador correto e para a ação correta dentro desse controlador. Para isso, ele utiliza o campo [Action] do URL. O restante do URL [/param1/param2/...] é composto por parâmetros opcionais que serão transmitidos à ação. O C de MVC é, neste caso, a string [Front Controller, Contrôleur, Action]. Se nenhum controlador puder processar a ação solicitada, o servidor web responderá que a ação solicitada não foi encontrada.
  1. Processamento
      • (continuação)
    • A ação selecionada pode utilizar os parâmetros parami que o [Front Controller] lhe transmitiu. Esses parâmetros podem provir de várias fontes:
      • do caminho [/param1/param2/...] do URL,
      • dos parâmetros [p1=v1&p2=v2] do URL,
      • dos parâmetros enviados pelo navegador junto com sua solicitação;
    • no processamento da solicitação do usuário, a ação pode precisar das camadas [métier] e [2b]. Uma vez processada a solicitação do cliente, ela pode gerar diversas respostas. Um exemplo clássico é:
      • uma página de erro, caso a solicitação não tenha sido processada corretamente
      • uma página de confirmação, caso contrário
    • a ação solicita que uma determinada vista seja exibida [3]. Essa vista exibirá dados que chamamos de modelo da vista. Esse é o M de MVC. A ação criará esse modelo M [2c] e solicitará que uma vista V seja exibida [3];
  1. resposta — a vista V selecionada utiliza o modelo M criado pela ação para inicializar as partes dinâmicas da resposta HTML que ela deve enviar ao cliente e, em seguida, envia essa resposta.

Para um serviço web / jSON, a arquitetura anterior é ligeiramente modificada:

  • em [4a], o modelo, que é uma classe Java, é transformado em uma string jSON por uma biblioteca jSON;
  • em [4b], essa string jSON é enviada ao navegador;

Agora, vamos esclarecer a relação entre a arquitetura web MVC e a arquitetura em camadas. Dependendo da definição que se atribui ao modelo, esses dois conceitos podem ou não estar relacionados. Consideremos uma aplicação web Spring MVC de camada única:

Se implementarmos a camada [Web] com o Spring MVC, teremos, de fato, uma arquitetura web MVC, mas não uma arquitetura multicamadas. Nesse caso, a camada [web] cuidará de tudo: apresentação, lógica de negócios e acesso aos dados. São as ações que realizarão esse trabalho.

Agora, vamos considerar uma arquitetura web multicamadas:

A camada [Web] pode ser implementada sem framework e sem seguir o modelo MVC. Temos, portanto, uma arquitetura multicamadas, mas a camada Web não implementa o modelo MVC.

Por exemplo, no mundo .NET, a camada [Web] acimaacima pode ser implementada com ASP.NET e MVC, e temos, então, uma arquitetura em camadas com uma camada [Web] do tipo MVC. Feito isso, é possível substituir essa camada ASP.NET MVC por uma camada ASP.NET clássica (WebForms), mantendo o restante (de negócio, DAO, ORM) inalterado. Temos, então, uma arquitetura em camadas com uma camada [Web] que não é mais do tipo MVC.

Em MVC, afirmamos que o modelo M era o da vista V, c.a.d, ou seja, o conjunto de dados exibidos pela vista V. É fornecida outra definição do modelo M de MVC:

Muitos autores consideram que o que está à direita da camada [Web] forma o modelo M do MVC. Para evitar ambiguidades, pode-se falar:

  • do modelo do domínio ao se referir a tudo o que está à direita da camada [Web]
  • do modelo da visualização, quando se refere aos dados exibidos por uma visualização V

Daqui em diante, o termo “modelo M” se referirá exclusivamente ao modelo de uma vista V.

16.3. Um projeto web / jSON com Spring MVC

O site [http://spring.io/guides] oferece tutoriais introdutórios para conhecer o ecossistema Spring. Seguiremos um deles para descobrir a configuração do Maven necessária para um projeto Spring MVC.

16.3.1. O projeto de demonstração

  • no [1], importamos um dos guias do Spring;
  • em [2], selecionamos o exemplo [Rest Service];
  • em [3], selecionamos o projeto Maven;
  • em [4], selecionamos a versão final do guia;
  • em [5], confirmamos;
  • em [6], o projeto importado;

Os serviços web acessíveis por meio de URL padrão e que fornecem texto jSON são frequentemente chamados de serviços REST (REpresentational State Transfer). Um serviço é considerado Restful se respeitar determinadas regras.

Vamos agora examinar o projeto importado, começando pela sua configuração do Maven.

16.3.2. Configuração do Maven

O arquivo [pom.xml] é o seguinte:


<?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.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <start-class>hello.Application</start-class>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
  • linhas 6-8: as propriedades do projeto Maven. Falta uma tag [<packaging>] indicando o tipo do arquivo gerado pela compilação do Maven. Na ausência dela, é utilizado o tipo [jar]. O aplicativo é, portanto, um aplicativo executável do tipo console, e não um aplicativo web, caso em que o pacote seria [war];
  • linhas 10-14: o projeto Maven possui um projeto pai [spring-boot-starter-parent]. É ele que define a maior parte das dependências do projeto. Elas podem ser suficientes, caso em que não se adicionam outras, ou não, caso em que se adicionam as dependências que faltam;
  • linhas 17-20: o artefato [spring-boot-starter-web] traz consigo as bibliotecas necessárias para um projeto Spring MVC do tipo serviço web, no qual não há visualizações geradas. Esse artefato traz consigo um grande número de bibliotecas, incluindo as de um servidor Tomcat embutido. É nesse servidor que a aplicação será executada;

As bibliotecas incluídas nesta configuração são muito numerosas:

Acima, vemos os três arquivos do servidor Tomcat.

16.3.3. A arquitetura de um serviço Spring [web / jSON]

Para um serviço web / jSON, o Spring MVC implementa o modelo MVC da seguinte maneira:

  • em [4a], o modelo, que é uma classe Java, é transformado na string jSON por uma biblioteca jSON;
  • em [4b], essa sequência jSON é enviada ao navegador;

16.3.4. O controlador C

  

O aplicativo importado possui o seguinte controlador:


package hello;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping("/greeting")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}
  • linha 9: a anotação [@RestController] transforma a classe [GreetingController] em um controlador Spring, ou seja, seus métodos são registrados para processar URL. Já vimos a anotação semelhante [@Controller]. O resultado dos métodos desse controlador era um tipo [String], que correspondia ao nome da visualização a ser exibida. Aqui é diferente. Os métodos de um controlador do tipo [@RestController] retornam objetos que são serializados para serem enviados ao navegador. O tipo de serialização realizada depende da configuração do Spring MVC. Aqui, eles serão serializados como jSON. É a presença de uma biblioteca jSON nas dependências do projeto que faz com que o Spring Boot, por meio de autoconfiguração, configure o projeto dessa maneira;
  • linha 14: a anotação [@RequestMapping] indica o URL que o método processa, neste caso, o URL [/greeting];
  • linha 15: já explicamos a anotação [@RequestParam]. O resultado retornado pelo método é um objeto do tipo [Greeting].
  • linha 12: um inteiro longo de tipo atômico. Isso significa que ele suporta concorrência de acesso. Várias threads podem querer incrementar a variável [counter] ao mesmo tempo. Isso ocorrerá de forma segura. Uma thread só pode ler o valor do contador se a thread que está modificando-o tiver concluído sua modificação.

16.3.5. O modelo M

O modelo M gerado pelo método anterior é o seguinte objeto [Greeting]:

  

package hello;

public class Greeting {

    private final long id;
    private final String content;

    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }

    public long getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

A transformação jSON desse objeto criará a sequência de caracteres {"id":n,"content":"texto"}. No final, a sequência jSON gerada pelo método do controlador terá o seguinte formato:

{"id":2,"content":"Hello, World!"}

ou

{"id":2,"content":"Hello, John!"}

16.3.6. Execução

  

A classe [Application.java] é a classe executável do projeto. Seu código é o seguinte:


package hello;

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

@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Já vimos e explicamos esse código no exemplo anterior. Vamos executar o projeto:

 

Obtemos os seguintes registros de console:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.9.RELEASE)

2014-11-28 15:22:55.005  INFO 3152 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 3152 (started by ST in D:\data\istia-1415\spring mvc\dvp-final\gs-rest-service)
2014-11-28 15:22:55.046  INFO 3152 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@62e136d3: startup date [Fri Nov 28 15:22:55 CET 2014]; root of context hierarchy
2014-11-28 15:22:55.762  INFO 3152 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-11-28 15:22:56.567  INFO 3152 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-11-28 15:22:56.738  INFO 3152 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2014-11-28 15:22:56.740  INFO 3152 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.56
2014-11-28 15:22:56.869  INFO 3152 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-11-28 15:22:56.870  INFO 3152 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1827 ms
2014-11-28 15:22:57.478  INFO 3152 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-11-28 15:22:57.481  INFO 3152 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-11-28 15:22:57.685  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.879  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public hello.Greeting hello.GreetingController.greeting(java.lang.String)
2014-11-28 15:22:57.884  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.885  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.906  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] para o manipulador do tipo [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.907  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] para o manipulador do tipo [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:58.231  INFO 3152 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-11-28 15:22:58.318  INFO 3152 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-11-28 15:22:58.319  INFO 3152 --- [           main] hello.Application                        : Started Application in 3.788 seconds (JVM running for 4.424)
  • linha 13: o servidor Tomcat é iniciado na porta 8080 (linha 12);
  • linha 17: o servlet [DispatcherServlet] está presente;
  • linha 20: o método [GreetingController.greeting] foi detectado;

Para testar a aplicação web, solicitamos o URL [http://localhost:8080/greeting]:

 

Recebemos corretamente a sequência jSON esperada. Pode ser interessante verificar os cabeçalhos HTTP enviados pelo servidor. Para isso, vamos usar a extensão do Chrome chamada [Advanced Rest Client] (Chrome / Ctrl-T / Menu [Applications] / [Advanced Rest Client] — ver Anexos, parágrafo 23.11):

  • em [1], o URL solicitado;
  • em [2], é utilizado o método GET;
  • em [3], a resposta jSON;
  • em [4], o servidor indicou que estava enviando uma resposta no formato jSON;
  • em [5], solicita-se o mesmo URL, mas desta vez com um POST;
  • em [7], as informações são enviadas ao servidor no formato [urlencoded];
  • em [6], o parâmetro name com seu valor;
  • em [8], o navegador informa ao servidor que está enviando as informações [urlencoded];
  • em [9], a resposta jSON do servidor;

16.3.7. Criação de um arquivo executável

Agora criamos um arquivo executável:

  • em [1]: executamos um alvo do Maven;
  • em [2]: há dois alvos (goals): [clean] para excluir a pasta [target] do projeto Maven e [package] para regenerá-la;
  • em [3]: a pasta [target] gerada será criada nessa pasta;
  • em [4]: gera-se o alvo;

Nos logs que aparecem no console, é importante verificar se o plugin [spring-boot-maven-plugin] está presente. É ele que gera o arquivo executável (veja [pom.xml] abaixo):


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
</build>
[INFO] --- spring-boot-maven-plugin:1.1.0.RELEASE:repackage (default) @ gs-rest-service ---

Na linha de comando, acesse a pasta gerada:


D:\Temp\wksSTS\gs-rest-service\target>dir
 ...
11/06/2014  15:30    <DIR>          classes
11/06/2014  15:30    <DIR>          generated-sources
11/06/2014  15:30        11 073 572 gs-rest-service-0.1.0.jar
11/06/2014  15:30             3 690 gs-rest-service-0.1.0.jar.original
11/06/2014  15:30    <DIR>          maven-archiver
11/06/2014  15:30    <DIR>          maven-status
...
  • linha 5: o arquivo gerado;

Esse arquivo é executado da seguinte maneira:


D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.0.RELEASE)

2014-06-11 15:32:47.088  INFO 4972 --- [           main] hello.Application
                  : Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...

Agora que o aplicativo web foi iniciado, é possível acessá-lo com um navegador:

 

16.3.8. Implantar o aplicativo em um servidor Tomcat

Assim como foi feito no projeto anterior, modificamos o arquivo [pom.xml] da seguinte maneira:


<?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.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>
    <packaging>war</packaging>

    ...
</project>
  • linha 9: é necessário indicar que será gerado um arquivo WAR (Web ARchive);

Além disso, é necessário configurar a aplicação web. Na ausência do arquivo [web.xml], isso é feito com uma classe que herda de [SpringBootServletInitializer]:

  

A classe [ApplicationInitializer] é a seguinte:


package hello;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

public class ApplicationInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

}
  • linha 6: a classe [ApplicationInitializer] estende a classe [SpringBootServletInitializer];
  • linha 9: o método [configure] é redefinido (linha 8);
  • linha 10: é fornecida a classe que configura o projeto;

Para executar o projeto, pode-se proceder da seguinte forma:

  • no [1-2], execute o projeto em um dos servidores registrados no IDE Eclipse;

Feito isso, pode-se acessar o URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] em um navegador:

 

16.4. Conclusion

Introduzimos um tipo de projeto Spring MVC em que a aplicação web envia um fluxo jSON para o navegador. Vamos agora desenvolver uma aplicação web / jSON para disponibilizar na web o banco de dados [dbproduitscategories] estudado nos capítulos anteriores.