Skip to content

16. Introdução ao Spring MVC

16.1. O papel do Spring MVC numa aplicação Web

Vamos contextualizar o Spring MVC no desenvolvimento de uma aplicação Web. Na maioria das vezes, esta será construída com base numa arquitetura multicamadas, como a seguinte:

  • a camada [Web] é a camada em contacto com o utilizador da aplicação Web. Este interage com a aplicação Web através de páginas Web visualizadas por um navegador. É nesta camada que se situa o Spring MVC e apenas nesta 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. Esta camada utiliza dados provenientes do utilizador através da camada [Web] e da camada SGBD através da camada [DAO];
  • A camada [DAO] (Data Access Objects), a camada [ORM] (Object Relational Mapper) e o controlador JDBC gerem 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 uma base de dados relacional. Uma especificação denominada JPA (Java Persistence API) permite abstrair-se do ORM utilizado, caso este implemente essas especificações. Será esse o caso neste tutorial e, por isso, passaremos a designar a camada ORM como a 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 – Vista – Controlador) da seguinte forma:

O processamento de um pedido de um cliente decorre da seguinte forma:

  1. pedido — os URL solicitados têm o formato http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... O [Front Controller] utiliza um ficheiro de configuração ou anotações Java para «encaminhar» o pedido para o controlador correto e para a ação correta dentro desse controlador. Para tal, utiliza o campo [Action] do URL. O restante do URL [/param1/param2/...] é constituído por parâmetros opcionais que serão transmitidos à ação. O C de MVC é, neste caso, a cadeia [Front Controller, Contrôleur, Action]. Se nenhum controlador puder processar a ação solicitada, o servidor Web responderá que a ação URL 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. Estes 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 juntamente com o seu pedido;
    • no processamento do pedido do utilizador, a ação pode necessitar da camada [métier] [2b]. Uma vez processado o pedido do cliente, este pode gerar várias respostas. Um exemplo clássico é:
      • uma página de erro, caso a solicitação não tenha podido ser processada corretamente
      • uma página de confirmação, caso contrário
    • a ação solicita que uma determinada vista seja apresentada [3]. Esta vista irá apresentar dados a que se chama modelo da vista. É o M de MVC. A ação irá criar este modelo M [2c] e solicitar que uma vista V seja apresentada [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 deve enviar ao cliente e, em seguida, envia essa resposta.

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

  • em [4a], o modelo, que é uma classe Java, é transformado numa cadeia jSON por uma biblioteca jSON;
  • em [4b], esta cadeia jSON é enviada para o navegador;

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

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

Agora, consideremos uma arquitetura web multicamadas:

A camada [Web] pode ser implementada sem framework e sem seguir o modelo MVC. Temos, então, 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 MVC, obtendo-se assim uma arquitetura em camadas com uma camada [Web] do tipo MVC. Feito isto, é possível substituir esta camada ASP.NET MVC por uma camada ASP.NET clássica (WebForms), mantendo o resto (de negócio, DAO, ORM) inalterado. Temos então uma arquitetura em camadas com uma camada [Web] que já não é do tipo MVC.

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

Muitos autores consideram que o que se encontra à direita da camada [Web] constitui o modelo M do MVC. Para evitar ambiguidades, pode-se referir-se:

  • do modelo do domínio quando nos referimos a tudo o que está à direita da camada [Web]
  • do modelo da vista, quando nos referimos aos dados apresentados por uma vista V

Daqui em diante, o termo «modelo M» designará exclusivamente o modelo de uma vista V.

16.3. Um projeto web / jSON com Spring MVC

O site [http://spring.io/guides] disponibiliza tutoriais de introdução para explorar o ecossistema Spring. Vamos seguir 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 através de URL padrão e que fornecem texto jSON são frequentemente designados por serviços REST (REpresentational State Transfer). Um serviço é considerado Restful se respeitar determinadas regras.

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

16.3.2. Configuração do Maven

O ficheiro [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 baliza [<packaging>] que indique o tipo do ficheiro produzido pela compilação do Maven. Na ausência desta, é utilizado o tipo [jar]. A aplicação é, portanto, uma aplicação executável do tipo consola, e não uma aplicação web, caso em que o pacote seria [war];
  • linhas 10-14: o projeto Maven tem um projeto pai [spring-boot-starter-parent]. É este que define a maior parte das dependências do projeto. Estas podem ser suficientes, caso em que não se adicionam mais, ou não, caso em que se adicionam as dependências em falta;
  • linhas 17-20: o artefacto [spring-boot-starter-web] traz consigo as bibliotecas necessárias para um projeto Spring MVC do tipo serviço web, onde não há vistas geradas. Este artefacto inclui um grande número de bibliotecas, incluindo as de um servidor Tomcat incorporado. É neste 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 forma:

  • em [4a], o modelo, que é uma classe Java, é transformado numa cadeia jSON por uma biblioteca jSON;
  • em [4b], esta cadeia jSON é enviada para o navegador;

16.3.4. O controlador C

  

A aplicação importada tem 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] num controlador Spring, ou seja, os seus métodos são registados 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 vista a apresentar. Aqui é diferente. Os métodos de um controlador do tipo [@RestController] devolvem objetos que são serializados para serem enviados ao navegador. O tipo de serialização realizada depende da configuração do Spring MVC. Aqui, 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 autoconfiguração, configure o projeto desta forma;
  • linha 14: a anotação [@RequestMapping] indica o URL que o método processa, neste caso o URL [/greeting];
  • linha 15: já explicámos a anotação [@RequestParam]. O resultado devolvido pelo método é um objeto do tipo [Greeting].
  • linha 12: um inteiro longo de tipo atómico. Isto significa que suporta acesso concorrente. Várias threads podem querer incrementar a variável [counter] ao mesmo tempo. Isto será feito de forma segura. Uma thread só pode ler o valor do contador se a thread que o está a modificar tiver concluído a sua modificação.

16.3.5. O modelo M

O modelo M produzido 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 deste objeto criará a cadeia de caracteres {"id":n,"content":"texto"}. No final, a cadeia jSON produzida 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. O 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á nos deparámos com este código e explicámo-lo no exemplo anterior. Vamos executar o projeto:

 

Obtêm-se os seguintes registos da consola:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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 inicia na porta 8080 (linha 12);
  • linha 17: o servlet [DispatcherServlet] está presente;
  • linha 20: o método [GreetingController.greeting] foi detetado;

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

 

Recebemos corretamente a cadeia jSON esperada. Pode ser interessante ver os cabeçalhos HTTP enviados pelo servidor. Para tal, vamos utilizar a extensão do Chrome denominada [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 a enviar 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 na forma [urlencoded];
  • em [6], o parâmetro «name» com o seu valor;
  • em [8], o navegador indica ao servidor que lhe está a enviar informações [urlencoded];
  • em [9], a resposta jSON do servidor;

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

Vamos agora criar um arquivo executável:

  • em [1]: executamos um alvo Maven;
  • em [2]: existem dois objetivos (goals): [clean] para eliminar a pasta [target] do projeto Maven e [package] para a regenerar;
  • em [3]: a pasta [target] gerada será criada nesta pasta;
  • em [4]: é gerado o alvo;

Nos registos que aparecem na consola, é importante verificar se o plugin [spring-boot-maven-plugin] aparece. É este que gera o arquivo executável (ver [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 consola, acedemos à 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;

Este ficheiro é executado da seguinte forma:


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 a aplicação web está em execução, pode-se aceder à mesma através de um navegador:

 

16.3.8. Implementar a aplicação num servidor Tomcat

Tal como foi feito no projeto anterior, alteramos o ficheiro [pom.xml] da seguinte forma:


<?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 se vai gerar um arquivo WAR (Web ARchive);

Além disso, é necessário configurar a aplicação web. Na ausência do ficheiro [web.xml], isto é feito através de 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], executa-se o projeto num dos servidores registados no IDE Eclipse;

Feito isto, pode-se aceder ao URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] num 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 a base de dados [dbproduitscategories] estudada nos capítulos anteriores.