Primeiros passos no uso do Spring MVC

O Spring MVC, uma das partes do framework Spring, é um framework no estilo web maduro e capaz de responder as ações, com uma grande variedade de recursos e opções destinadas a manipulação de vários casos de usos focados ou não na web. Todos esses recursos podem tornar a vida do neófito bastante difícil. Neste artigo, será mostrado a essa audiência apenas pequenas tarefas que existem para por uma aplicação com Spring MVC para funcionar (isso é, considere o exemplo desse artigo como a aplicação Spring MVC mais simples do mundo), e é nisso que veremos no restante desse artigo.

Para os propósitos desse artigo, assumiremos que você seja familiarizado com Java, Spring e o modelo de programação básico para Servlet, mas não esteja familiarizado com o Spring MVC. Após ler esse artigo, os interessados podem aprender mais sobre o Spring MVC através do artigo Spring MVC 3 Showcase,de Keith Donald, ou de vários outros recursos on-line ou impressos que cobrem o tema.

Uma observação sobre dependências e sistemas de compilação: esse artigo não assume que você esteja usando um sistema particular, como Maven, Gradle ou Ant. Um arquivo exemplo mínimo usando Maven é incluído no final desse artigo.

O Spring MVC inclui muitos dos mesmos conceitos básicos de outros frameworks MVC. Requisições chegam ao framework através de um Front Controller. No caso do Spring MVC, ele é um Servlet Java chamado DispatcherServlet. Pense no DispatcherServlet como um porteiro. Ele não executa nenhuma lógica de negócio ou web, mas ao invés disso delega classes POJO chamadas Controllers onde o trabalho real é feito (no todo ou via um back-end). Quando o trabalho for realizado, é responsabilidade dos Views produzir a saída no formato adequado (seja uma página JSP, um modelo do Velocity ou uma resposta JSON). Strategies são usadas para decidir qual Controller (e qual método dele) manipula a requisição, e qual o View que gerará a resposta. O contêiner do Spring é usado para ligar todas essas peças. Tudo isso se parece com algo como isso:

Inicializando o DispatcherServlet e o contêiner do Spring Container

Como mencionado, todas as requisições fluem através do DispatcherServlet. Como qualquer outro Servlet em uma aplicação Java EE, precisamos informar o contêiner para carregar esse Servlet na inicialização da aplicação através do arquivo WEB-INF/web.xml. O DispatcherServlet também é responsável por carregar um ApplicationContext que é usado para executar ligações e injeção de dependências dos componentes gerenciados. Nessa base, especificamos alguns parâmetros de inicialização para o Servlet que configura a ApplicationContext. Vamos dar uma olhada no arquivo web.xml:

WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>		

	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

Várias coisas estão sendo feitas aqui:

  • Registramos o DispatcherServlet como um Servlet chamado appServlet
  • Mapeamos esse Servlet para manipular requisições (relativas ao caminho da aplicação) iniciados com  “/”
  • Usamos o parâmetro de inicialização ContextConfigLocation para personalizar a localização do arquivo XML de configuração do ApplicationContext  que é carregado pelo DispatchServlet, ao invés de confiar no local padrão <servletname>-context.xml).

O Controller

Agora vamos criar um controlador mínimo:


package xyz.sample.baremvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "WEB-INF/views/home.jsp";
}
}

Vamos percorrer os aspectos chaves dessa classe:

  • A classe foi anotada com a palavra-chave @Controller, indicando que se trata de um Controlador para o Spring MVC capaz de manipular requisições web. Por @Controller ser uma especialização da anotação Stereotype @Component, a classe será automaticamente detectada pelo contêiner do Spring como parte do processo de escaneamento de componentes, criando uma definição de bean e permitindo as intâncias serem injetadas como depêndencias como qualquer outro componente gerenciado pelo Spring.
  • O método home foi anotado com a palavra-chave @RequestMapping, especificando que esse método deve manipular requisições web do caminho “/”, isso é, da raiz de sua aplicação.
  • O método home simplemente mostra uma mensagem e retorna WEB-INF/views/home.jsp, indicando a visão que deve manipular a resposta, nesse caso uma página JSP (Se escrever o caminho completo para o arquivo, incluindo o prefixo WEB-INF, parecer errado para você, você está certo. Nós lidaremos com isso mais tarde).

Agora, precisamos criar a visão. Esse página JSP simplesmente mostra uma saudação.

Espere, e se alguém não quiser configurar o Spring via XML?

O tipo padrão do Application Context carregado pelo DispatcheServlet espera pelo carregamento de pelo menor um arquivo XML com as definições do Bean. Como vocÊ verá, também permitiremos que o Spring carregue configurações baseadas no Java, junto com o XML.

WEB-INF/views/home.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
	<head>
		<title>Home</title>
	</head>
	<body>
		<h1>Hello world!</h1>
	</body>
</html>

Finalmente, como mencionado anteriormente, precisamos criar um arquivo de definição para o Application Context.

WEB-INF/spring/appServlet/servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

	<!-- Scans within the base package of the application for @Components to configure as beans -->
	<!-- @Controller, @Service, @Configuration, etc. -->
	<context:component-scan base-package="xyz.sample.baremvc" />

	<!-- Enables the Spring MVC @Controller programming model -->
	<mvc:annotation-driven />

</beans>

Vamos examinar o conteúdo desse arquivo:

  • Você pode notar que alguns namespaces do Spring XML são usados: contextmvc, e o padrão beans
  • A declaração <context:component-scan> garante que o contêiner do Spring fará scaneamento por componentes, de forma que qualquer código com a anotação @Component é descoberto automaticamente. Você pode notar, que por motivos de eficiência, limitamos (para xyz.sample.baremvc nesse caso) que parte do pacote o Spring deve scanear no classpath.
  • A declaração <mvc:annotation-driven> configura o suporte do Spring MVC a roteamento de requisições aos Controladores, assim como como coisas como Conversações, Formatação e Validação são manipuladas (com alguns padrões sensíveis baseados em quais bibliotecas estão presentes no classpath, e a habilidade de sobrecarrega-las, se necessário).
A aplicação web está pronta para ser executada agora. Assumindo que o contêiner Servlet (tcServer em nosso exemplo) está configurado para escutar em localhost:8080, inicie a aplicação e acesse a URL http://localhost:8080/baremvc pelo seu navegador para ver a seguinte tela:

Por mais trivial que seja, executar essa aplicação envolve todas as partes principais de uma aplicação Spring MVC. Vamos percorrer essas sequência e interações entre componentes:

  • Quando a aplicação é iniciada o DispatcherServlet é carregado e inicializado por causa da entrada em web.xml.
  • O DispatcherServlet carrega um Application Context baseado na anotações,  que foi configurado para escanear pelos componentes anotados através de uma expressão regular especificada pelo pacote base.
  • Os componentes anotados como o HomeController são detectados pelo contêiner.
  • A requisição HTPP ao endereço http://localhost:8080/baremvc alcança o mecanismo do Servlet e é roteado para nossa aplicação (baremvc).
  • O caminho “/” implícito no final da URL coincide com o regex que foi registrado pelo DispatcherServlet, e a requisição é roteada para ele.
  • O DispatcherServlet precisa decidir o que fazer com a requisição. Ele usa um strategy chamado HandlerAdapter para decidir para onde rotear a requisição. O tipo do HandlerAdapter (ou tipos, já que pode ser aninhado) a ser usado pode ser personalizado, mas por padrão, um strategy baseado em anotações é usado, que direciona as requisições para os métodos adequados na classes anotadas com @Controller, baseados nos critérios de coincidência nas anotações de @RequestMapping encontradas nessas classes. Nesse caso, o regex do método home é chamado para manipular o evento.
  • O método home faz o seu trabalho, nesse caso imprimir algo na saída do sistema. Em seguida retorna uma string que é uma dica (nesse caso, uma muito explícita, WEB-INF/views/home.jsp) para ajudar a escolher a visão que renderiza a resposta.
  • O DispatcherServlet confia a um strategy, chamado  ViewResolver para resolver qual Visão é responsável pela renderização da resposta. Isso pode ser configurado de acordo com a necessidade da aplicação (de forma simples ou aninhada), mas por padrão, um InternalResourceViewResolver é usado. Esse é um jeito simples de produzir um JstlView que simplesmente delegue ao mecanismo de Servlet um RequestDispatcher a ser renderizado, e dessa forma poder usar tanto páginas JSP quanto HTML.
  • O mecanismo de Servlet renderiza a resposta através do JSP especificado.

Passando a próximo nível

Nesse ponto, temos uma aplicação que certamente de qualifica como a mais simples aplicação Spring MVC do mundo, mas francamente, não condiz realmente com o espirito dessa descrição. Vamos evoluir as coisas para outro nível.

Como mencionado anteriormente, não é apropriado  codificar diretamente  uma caminho para uma visão em um controlador, como nosso controlador faz. Um acoplamento mais fraco e mais lógico entre os controladores e visões, com os controladores focados em executar a lógica de negócio ou web, e geralmente agnóstico a detalhes específicos como caminhos para visões ou JSP, é um exemplo de separação de preocupações. Isso permite muito mais reuso tanto do controlador quanto da visão, e uma evolução mais fácil de cada um de forma isolado do ouro, com possivelmente pessoas diferentes trabalhando em cada tipo diferente de código.

Essencialmente, o código do controlador idealmente precisa ser algo como essa variação, onde um nome de visão puramente lógico (seja simples ou compostos) é retornado:


//...
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
}

ViewResolver Strategy do Spring MVC é normalmente o mecanismo a ser usado para conseguir esse acoplamento entre controlador e visão. Como mencionado, na ausência da configuração de um ViewResolver específico, o Spring MVC configura um padrão mínimo como InternalResourceViewResolver, um resolvedor bem simples que produz um JstlView.Existem outros resolvedores em potencial que podemos usar, mas para obter um nível melhor de desacoplamento, tudo que precisamos fazer de fato é configurar nossa própria instância de InternalResourceViewResolver com uma configuração ligeiramente modificada. O InternalResourceViewResolver usa um strategy bem simples; ele simplesmente pega o nome da visão retornado pelo controlador, e anexa um prefixo opcional (vazio por padrão), e anexa um sufixo opcional (vazio por padrão), e alimenta esse caminho resultante ao JstlView criado. O JstlView então delega o RequestDispatcher do mecanismo do Servlet o trabalho real, isso é, renderizar o modelo. Além disso, para permitir que o controlador retorne os nomes lógicos das visões, como home, ao invés do caminho específico como WEB-INF/views/home.jsp, precisamos simplesmente configurar esse resolvedor com o prefixo WEB-INF/views e o sufixo .jsp,  de forma que sejam anexados, respectivamente, ao nome lógico retornado pelo controlador.

 

Uma forma fácil de configurar a instância do resolvedor é introduzir o uso da configuração de contêiner baseado em Java do Spring, com o resolver como uma definição de bean:


package xyz.sample.baremvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
public class AppConfig {
// Resolve logical view names to .jsp resources in the /WEB-INF/views directory
@Bean
ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}

Nós já estamos fazendo verificação de componente, portanto como @Cofiguration é um @Component, essa nova configuração com o bean do resolvedor é automaticamente selecionada pelo contêiner do Spring. Em seguido o Spring MVC escaneia todos os beans e encontra o resolvedor.

This is a fine approach, but some people may instead prefer to configure the resolver as a bean in the XML definition file, e.g.

 

<!-- Resolve logical view names to .jsp resources in the /WEB-INF/views directory -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/WEB-INF/views/" />
	<property name="suffix" value=".jsp" />
</bean>

É difícil nesse caso dizer qual abordagem particular é melhor, de forma que é uma questão de preferência pessoal nesse caso (e podemos ver aqui uma das forças do Spring, sua natureza flexível).

Manipulando a entrada do usuário

Quase todas as aplicações  web precisam pegar algum pegar alguma entrada de dados de um cliente, executar alguma ação com essa entrada, e retornar ou renderizar o resultado. Existem várias formas de capturar a entrada de dados em uma aplicação Spring MVC, e várias formas de renderizar o resultado, mas vamos mostrar aqui uma dessas variações. No nosso exemplo simples, iremos modificar nosso HomeController para adicionar um novo método que recebe duas strings, compara as duas e retorna o resultado.


package xyz.sample.baremvc;
import java.util.Comparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@Autowired
Comparator comparator;
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
@RequestMapping(value = "/compare", method = RequestMethod.GET)
public String compare(@RequestParam("input1") String input1,
@RequestParam("input2") String input2, Model model) {
int result = comparator.compare(input1, input2);
String inEnglish = (result < 0) ? "less than" : (result > 0 ? "greater than" : "equal to");
String output = "According to our Comparator, '" + input1 + "' is " + inEnglish + "'" + input2 + "'";
model.addAttribute("output", output);
return "compareResult";
}
}

Elementos chave do código:

  • Estamos usando uma outra anotação @RequestMapping para criar requisições terminadas com o caminho /compare para o novo método compare.
  • Esperamos que quem chame o método passe as duas strings como parâmetro como parte da requisição GET, de forma que possamos captura-las pela anotação @RequestParam. Observe que estamos confiando na manipulação padrão para essa anotação, que assume que esses parâmetros são obirgatórios. O cliente receberá uma mensagem de erro HTTP 400 se esses parâmetros estiverem ausentes. Observe também que esse é apenas uma maneira de passa parâmetros para uma aplicação Spring MVC. Por exemplo, é fácil capturar parâmetros que são embutidos na URL requisitada, para uma abordagem mais simples.
  • Usamos nossa instância do Comparador para comparar as duas strings.
  • Empacotamos o resultado da comparação no objeto Model sob a chave result, de forma que pode ser acessado pela visão.  Pense nesse objeto Model como um hashmap, em termos simples.

Mesmo podendo ter modificado nossa visão existente para exibir o resultado da comparação, usaremos ao invés disso um novo modelo de visão:

WEB-INF/views/compareResult.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
	<head>
		<title>Result</title>
	</head>
	<body>
		<h1><c:out value="${output}"></c:out></h1>
	</body>
</html> 

Finalmente, precisamos suprir um controlador com uma instância de Comparator para usar. Temos o campo comparator anotado no controlador com a anotação @Autowired do Spring (que irá ser detectada automaticamente  após a detecção do controlador) e instrui o contêiner do Spring a injetar um Comparator nesse campo. Além disso, precisamos garantir que o contêiner esteja disponível. Para esse propósito, uma implementação mínima de Comparator é criada, que simplesmente faz uma comparação sensível à caixa. Para efeito de simplificação, essa classe foi anotada com a anotação Stereotype @Service do Spring, um tipo de @Component e assim sendo automaticamente detectada pelo contêiner do Spring como parte  do processo de escaneamento de componentes do contêiner, e injetado no controlador.


package xyz.sample.baremvc;
import java.util.Comparator;
import org.springframework.stereotype.Component;
@Service
public class CaseInsensitiveComparator implements Comparator {
public int compare(String s1, String s2) {
assert s1 != null && s2 != null;
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
}
}

Observe que poderíamos apenas ter declarado uma instância dele no contêiner através de uma definição de @Bean baseada em Java em uma classe @Configuration, ou em uma definição de bean baseada em XML, e certamente essas variações podem ser preferíveis em muitos casos por oferecerem um maior nível de controle.

Podemos agora iniciar a aplicação  com uma URL da forma: http://localhost:8080/baremvc/compare?input1=Donkey&input2=dog para testar o novo código:

Anexo – Dependências

O código acima deve funcionar independentemente de qual sistema de compilação você usar (atualmente, geralmente Gradle ou Maven). Abaixo segue um modelo de arquivo POM do Maven para compilar o projeto acima, que pode ser usado com base, para entender as dependências necessárias.

 

<?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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>xyz.sample</groupId>
	<artifactId>baremvc</artifactId>
	<name>Sprring MVC sample project</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>3.0.6.RELEASE</org.springframework-version>
		<org.slf4j-version>1.6.1</org.slf4j-version>
	</properties>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

		<!-- CGLIB, only required and used for @Configuration usage -->
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib-nodep</artifactId>
			<version>2.2</version>
		</dependency>

		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.16</version>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>

		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
	<repositories>
		<repository>
			<id>org.springframework.maven.release</id>
			<name>Spring Maven Release Repository</name>
			<url>http://maven.springframework.org/release</url>
			<releases><enabled>true</enabled></releases>
			<snapshots><enabled>false</enabled></snapshots>
		</repository>
		<!-- For testing against latest Spring snapshots -->
		<repository>
			<id>org.springframework.maven.snapshot</id>
			<name>Spring Maven Snapshot Repository</name>
			<url>http://maven.springframework.org/snapshot</url>
			<releases><enabled>false</enabled></releases>
			<snapshots><enabled>true</enabled></snapshots>
		</repository>
		<!-- For developing against latest Spring milestones -->
		<repository>
			<id>org.springframework.maven.milestone</id>
			<name>Spring Maven Milestone Repository</name>
			<url>http://maven.springframework.org/milestone</url>
			<snapshots><enabled>false</enabled></snapshots>
		</repository>
	</repositories>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>${java-version}</source>
					<target>${java-version}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<configuration>
					<warName>baremvc</warName>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>tomcat-maven-plugin</artifactId>
				<version>1.1</version>
			</plugin>
		</plugins>
	</build>
</project>

ARTIGOS SIMILARES

TRADUZIDO DE