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: context, mvc, 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 (paraxyz.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).
http://localhost:8080/baremvc
pelo seu navegador para ver a seguinte tela:- 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, umInternalResourceViewResolver
é usado. Esse é um jeito simples de produzir umJstlView
que simplesmente delegue ao mecanismo de Servlet umRequestDispatcher
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";
}
}
O 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
- Maven PAR Plugin 1.0.0.M1
- First Eclipse Gemini Blueprint Milestone Ships
- Obtaining Spring 3 Artifacts with Maven
- Logging Dependencies in Spring
- Spring Integration 2.0 GA Released