Introdução ao Spring Framework
- Preparação
- Anotações Java
- Criando um Hello World
- Criando uma listagem de carros
- Funcionalidade de adicionar carros
- Validação do formulário
- Funcionalidade de editar carros
- Prefixo comum para mapeamentos do controller
- Fazendo páginas multi-idiomas
Preparação
Novo projeto de aplicação web
- Crie um novo projeto de Aplicação Web pelo NetBeans.
- Nome do projeto: “SpringWebApp”
- Servidor: GlassFish
- Frameworks: escolha Spring Web MVC, na configuração, escolha a versão 4.0.1 e deixe marcada a opção Incluir JSTL.
- Clique em Finalizar
Exclusão de arquivos desnecessários
Na área de Páginas Web, remova todos os arquivos .jsp e .xml criados pelo NetBeans (não vamos precisar deles).
Só deixe as pastas WEB-INF/
e WEB-INF/jsp/
.
Adicionar biblioteca necessária
Clique o botão direito do mouse em Bibliotecas e selecione no menu de contexto a opção Adicionar Biblioteca, escolha Hibernate 4.3.x.
Anotações Java
O framework Spring trabalha com anotações para fazer as automatizações, eliminando muito código repetido.
Uma anotação em Java é uma instrução do tipo @Anotacao
inserida antes da definição de uma classe, de um método,
de um atributo ou de um parâmetros de método.
Por exemplo, na programação web em Java tradicional, usamos a anotação @WebServlet
para indicar para o servidor
GlassFish que a classe é um servlet.
Criando um Hello World
Primeiro, vamos criar uma classe HelloController
no pacote primavera.controller
com um ponto de entrada em /hi
.
Observe as anotações colocadas.
package primavera.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hi")
@ResponseBody
public String hi() {
return "Hello, world.";
}
}
Essas anotações não tem efeito se não configurarmos o Spring, para isso vamos criar duas classes AppConfig
e
ServletInitializer
dentro do pacote primavera.config
.
package primavera.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"primavera.controller"})
public class AppConfig {
}
package primavera.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
Depois que criar as configurações, execute o projeto e acesse http://localhost:8080/SpringWebApp/hi para ver se está funcionando, a resposta deve ser a que foi configurada no controller.
Observe que "/hi"
após o contexto da aplicação é o nome utilizado na anotação @RequestMapping
.
Criando uma listagem de carros
Vamos adicionar um controller mais complexo que vai envolver as outras camadas do padrão MVC: por enquanto só usamos o C (controller), agora vamos utilizar também o M (model) e o V (view).
A camada Model
Esta é a camada responsável pelos dados da aplicação. Criaremos dois elementos:
- A classe
Car
, que serve apenas para encapsular os campos de um carro. - A classe
CarDao
, que utiliza-se do padrão DAO (Data Access Object) para dar acesso aos dados.
Normalmente uma classe DAO faz acesso a um banco de dados, mas nesse exemplo, por enquanto, nosso “banco de dados” será uma lista normal, guardada na memória temporária do servidor.
Primeiro, crie a classe Car
no pacote primavera.domain
.
package primavera.domain;
import java.math.BigDecimal;
public class Car {
private String name;
private BigDecimal price;
// getters & setters
}
Depois, crie a classe CarDao
no pacote primavera.dao
. Observe que temos a anotação @Service
, ela serve para não
precisarmos instanciar essa classe nós mesmos quando a utilizarmos.
package primavera.dao;
import org.springframework.stereotype.Service;
import primavera.domain.Car;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Service
public class CarDao {
private final List<Car> carList = new ArrayList<>();
CarDao() {
Car car1 = new Car();
car1.setName("Mercedes SL");
car1.setPrice(BigDecimal.valueOf(123400));
carList.add(car1);
Car car2 = new Car();
car2.setName("BMW M6 Coupé");
car2.setPrice(BigDecimal.valueOf(125000));
carList.add(car2);
Car car3 = new Car();
car3.setName("Audi R8");
car3.setPrice(BigDecimal.valueOf(136100));
carList.add(car3);
}
public List<Car> findAll() {
return carList;
}
}
A camada View
Esta é a camada responsável pelo que o usuário vê. Normalmente, usamos arquivos .jsp para implementar a view.
Assim, na área de Páginas Web, criaremos o arquivo list.jsp
dentro da pasta WEB-INF/jsp/car/
.
<%@ page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Carros</title>
</head>
<body>
<h1>Carros</h1>
<c:forEach items="${carList}" var="car">
<p>
${car.name}: $${car.price}
</p>
</c:forEach>
</body>
</html>
A camada Controller
Para fazer a ligação entre as camadas Model e View, temos a camada Controller. Aqui, vamos criar uma nova classe
CarController
. Observe a anotação @Autowired
, com ela não precisamos instanciar manualmente o atributo.
Tem outra coisa importante para observar: no método carList(Model)
utilizamos um objeto model
para adicionar um
atributo chamado "carList"
, esse é utilizado pela view com ${carList}
.
package primavera.controller;
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 primavera.dao.CarDao;
import primavera.domain.Car;
import java.util.List;
@Controller
public class CarController {
@Autowired
private CarDao carDao;
@RequestMapping("/car/list")
public void carList(Model model) {
List<Car> carList = carDao.findAll();
model.addAttribute("carList", carList);
}
}
Configurações pendentes
O arquivo JSP ainda não pode ser encontrado
No controller, definimos o caminho "/car/list"
, esse também é o caminho usado para carregar a view
em WEB-INF/jsp/car/list.jsp
, mas para isso ocorrer, precisamos antes
adicionar uma configuração à AppConfig
para o Spring saber onde encontrar as views:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"primavera.controller"})
public class AppConfig {
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
}
A classe DAO não foi inicializada
Para que o Spring processe a anotação @Service
na classe DAO, é preciso informar na configuração para que o pacote
primavera.dao
também seja escaneado:
@ComponentScan(basePackages = {"primavera.dao", "primavera.controller"})
O código até agora
Você pode baixar o código até esse ponto aqui.
Funcionalidade de adicionar carros
Vamos adicionar a possibilidade de adicionar carros à listagem. Precisamos de três coisas:
- Criar um método
add(Car)
na classeCarDao
. - Criar dois métodos
carAdd()
ecarAdd(Car)
à classeCarController
. - Adicionar um formulário HTML em
WEB-INF/jsp/car/add.jsp
.
Alterando o DAO
Car car3 = new Car();
car3.setName("Audi R8");
car3.setPrice(BigDecimal.valueOf(136100));
carList.add(car3);
}
public void add(Car car) {
carList.add(car);
}
public List<Car> findAll() {
return carList;
}
}
Alterando o controller
Precisamos de dois métodos no controller configurados para o mesmo caminho /car/add
, um deles irá apenas carregar o
arquivo JSP utilizando o método HTTP GET, e o outro irá fazer o cadastro, mas aceitará apenas requisições HTTP POST.
Observe a nova anotação @ModelAttribute
, com a utilização dela, não precisamos obter os parâmetros do formulário
manualmente, como fazíamos utilizando servlets.
Isto quer dizer que o objeto do tipo Car
no segundo método já virá preenchido.
@RequestMapping("/car/list")
public void carList(Model model) {
List<Car> carList = carDao.findAll();
model.addAttribute("carList", carList);
}
@RequestMapping("/car/add")
public void carAdd() {
}
@RequestMapping(value = "/car/add", method = RequestMethod.POST)
public String carAdd(@ModelAttribute Car car) {
carDao.add(car);
return "redirect:/car/list";
}
}
Adicionando o formulário de cadastro
<%@ page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Carros</title>
</head>
<body>
<h1>Adicione um carro</h1>
<form:form method="POST">
<p>
Nome: <br>
<input type="text" name="name" />
</p>
<p>
Preço: <br>
<input type="number" name="price" />
</p>
<input type="submit" />
</form:form>
</body>
</html>
O código até agora
Você pode baixar o código até esse ponto aqui.
Validação do formulário
O framework Spring fornece uma forma conveniente para validar formulários.
Anotando a classe da entidade
Primeiro, vamos adicionar à classe Car
anotações que explicam os requisitos para cada campo:
public class Car {
@NotEmpty
private String name;
@Min(1000) @Max(5_000_000)
private BigDecimal price;
}
As anotações dizem que name
não pode estar vazio e price
deve estar entre mil e cinco milhões.
Alterando o controller para garantir a entrada válida
Precisamos adicionar outra anotação ao argumento do tipo Car
e adicionar um segundo argumento do tipo BindingResult
,
para saber o resultado da validação, utilizamos esse objeto para mandar retornar ao formulário, informando ao usuário
dos erros ocorridos.
@RequestMapping("/car/add")
public void carAdd() {
}
@RequestMapping(value = "/car/add", method = RequestMethod.POST)
public String carAdd(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
}
// validação bem sucedida
carDao.add(car);
return "redirect:/car/list";
}
}
Alterando o formulário para exibir os erros
<%@ page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Carros</title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1>Adicione um carro</h1>
<form:form method="POST" modelAttribute="car">
<p>
Nome: <br>
<input type="text" name="name" />
<form:errors path="name" cssClass="error" />
</p>
<p>
Preço: <br>
<input type="number" name="price" />
<form:errors path="price" cssClass="error" />
</p>
<input type="submit" />
</form:form>
</body>
</html>
Mantendo o formulário preenchido nos erros
Para isso, usamos as tags do Spring <form:input>
em vez dos <input>
convencionais do HTML:
<h1>Adicione um carro</h1>
<form:form method="POST" modelAttribute="car">
<p>
Nome: <br>
<form:input path="name" />
<form:errors path="name" cssClass="error" />
</p>
<p>
Preço: <br>
<form:input path="price" type="number" />
<form:errors path="price" cssClass="error" />
</p>
<input type="submit" />
</form:form>
</body>
</html>
É preciso também uma pequena alteração no controller para não haver erros de execução:
@RequestMapping("/car/list")
public void carList(Model model) {
List<Car> carList = carDao.findAll();
model.addAttribute("carList", carList);
}
@RequestMapping("/car/add")
public void carAdd(@ModelAttribute("car") Car car) {
}
@RequestMapping(value = "/car/add", method = RequestMethod.POST)
public String carAdd(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
O código até agora
Você pode baixar o código até esse ponto aqui.
Funcionalidade de editar carros
Nessa seção, vamos adicionar à aplicação a possibilidade de editar um carro.
Isso será feito em três passos:
- Vamos adicionar um identificador à entidade
Car
. Para que o carro a ser editado possa ser encontrado facilmente, é preciso adicionar um campoid
.
Identificador de carro
Na classe Car
, adicione um novo atributo (lembre-se adicionar os gets e sets):
public class Car {
private Long id;
@NotEmpty
private String name;
@Min(1000) @Max(5_000_000)
private BigDecimal price;
// getters & setters
E na classe CarDao
, crie uma variável estática para manter uma sequência dos carros adicionados:
@Service
public class CarDao {
private final List<Car> carList = new ArrayList<>();
private static long sequence = 0;
CarDao() {
Car car1 = new Car();
car1.setName("Mercedes SL");
car1.setPrice(BigDecimal.valueOf(123400));
this.add(car1);
Car car2 = new Car();
car2.setName("BMW M6 Coupé");
car2.setPrice(BigDecimal.valueOf(125000));
this.add(car2);
Car car3 = new Car();
car3.setName("Audi R8");
car3.setPrice(BigDecimal.valueOf(136100));
this.add(car3);
}
public void add(Car car) {
sequence += 1;
car.setId(sequence);
carList.add(car);
}
public List<Car> findAll() {
return carList;
}
}
Método de busca de carro
Adicione à CarDao
o método get(long)
para achar na lista um carro de certo id
:
public void add(Car car) {
sequence += 1;
car.setId(sequence);
carList.add(car);
}
public Car get(long id) {
for (Car car : carList) {
if (id == car.getId()) {
return car;
}
}
throw new IllegalArgumentException("Carro não encontrado: " + id);
}
public List<Car> findAll() {
return carList;
}
}
Interface para editar um carro
O formulário de editar carros é idêntico ao de adicionar, por isso vamos reutilizar o JSP, fazendo algumas modificações:
<body>
<h1>${car.id == null ? 'Adicione' : 'Edite'} um carro</h1>
<form:form method="POST" modelAttribute="car">
<form:hidden path="id" />
<p>
Nome: <br>
<form:input path="name" />
<form:errors path="name" cssClass="error" />
</p>
O formulário de edição de carros só poderá ser acessado pelos usuários se adicionar um novo método à classe CarController
:
@RequestMapping(value = "/car/add", method = RequestMethod.POST)
public String carAdd(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
}
// validação bem sucedida
carDao.add(car);
return "redirect:/car/list";
}
@RequestMapping("/car/edit/{id}")
public String carEdit(@PathVariable("id") Long id, Model model) {
Car car = carDao.get(id);
model.addAttribute("car", car);
return "/car/add";
}
}
Agora se você visitar, por exemplo, http://localhost:8080/SpringWebApp/car/edit/1, você verá o formulário preenchido com o Mercedes SL.
Ação para editar carros
O formulário de edição ainda não altera os dados de um carro, para isso ocorrer, será necessário providenciar a ação a ser executada quando o usuário envia novos dados.
Primeiramente, vamos adicionar um método edit(Car)
à classe CarDao
:
public Car get(long id) {
for (Car car : carList) {
if (id == car.getId()) {
return car;
}
}
throw new IllegalArgumentException("Carro não encontrado: " + id);
}
public void edit(Car car) {
for (int i = 0; i < carList.size(); i++) {
if (car.getId() == carList.get(i).getId()) {
carList.set(i, car);
return;
}
}
throw new IllegalArgumentException("Carro não encontrado: " + car.getId());
}
public List<Car> findAll() {
return carList;
}
}
Depois, vamos adicionar à CarController
o método que vai executar a ação de editar um carro.
@RequestMapping("/car/edit/{id}")
public String carEdit(@PathVariable("id") Long id, Model model) {
Car car = carDao.get(id);
model.addAttribute("car", car);
return "/car/add";
}
@RequestMapping(value = "/car/edit/{id}", method = RequestMethod.POST)
public String carEdit(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
}
// validação bem sucedida
carDao.edit(car);
return "redirect:/car/list";
}
}
Link para a tela de editar carro
A funcionalidade para alterar carros já está pronta, mas é importante que o usuário tenha uma forma fácil para acessar o formulário. Para isso, faça a seguinte modificação:
<body>
<h1>Carros</h1>
<c:forEach items="${carList}" var="car">
<p>
<c:url var="editUrl" value="/car/edit/${car.id}" />
${car.name}: $${car.price} <a href="${editUrl}">Editar</a>
</p>
</c:forEach>
</body>
</html>
Prefixo comum para mapeamentos do controller
Na classe CarController
, todas as URLs iniciam por “/car”. Nesses casos, o programador pode economizar digitadas
anotando a classe com @RequestMapping("/prefixo/comum")
. Dessa forma, o caminho definido nos métodos são adicionados a
esse prefixo.
Para você entender melhor, com as modificações abaixo em CarController
, o usuário acessará as páginas relativas ao
carro da mesma forma que antes:
@Controller
@RequestMapping("/car")
public class CarController {
@Autowired
private CarDao carDao;
@RequestMapping("/list")
public void carList(Model model) {
List<Car> carList = carDao.findAll();
model.addAttribute("carList", carList);
}
@RequestMapping("/add")
public void carAdd(@ModelAttribute("car") Car car) {
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String carAdd(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
}
// validação bem sucedida
carDao.add(car);
return "redirect:/car/list";
}
@RequestMapping("/edit/{id}")
public String carEdit(@PathVariable("id") Long id, Model model) {
Car car = carDao.get(id);
model.addAttribute("car", car);
return "/car/add";
}
@RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
public String carEdit(@ModelAttribute("car") @Valid Car car, BindingResult result) {
if (result.hasErrors()) {
// mostra o formulário novamente, com os erros
return "/car/add";
}
// validação bem sucedida
carDao.edit(car);
return "redirect:/car/list";
}
}
O código até agora
Você pode baixar o código até esse ponto aqui.
Fazendo páginas multi-idiomas
Com o framework Spring, é relativamente fácil criar páginas com internacionalização, isto é, páginas que exibem no idioma mais adequado a depender do cliente.
Como o servidor web detecta o idioma do cliente?
Antes, vamos entender como isso funciona: quando o browser acessa qualquer página, envia um cabeçalho chamado
Accept-Language
com os idiomas preferidos, se a aplicação estiver internacionalizada, irá verificar se tem um dos
idiomas solicitados ou entrega um idioma padrão da aplicação. Se a aplicação não estiver internacionalizada, esse
cabeçalho será ignorado.
Por exemplo, o browser dos brasileiros normalmente enviam:
Accept-Language: pt-br, pt, en-us, en
Isso quer dizer as seguinte ordem de preferência:
- Português (Brasil)
- Português
- Inglês (Americano)
- Inglês
Uma página inicial
Vamos exercitar a ideia de internacionalização com uma página inicial:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Bem vindo</title>
</head>
<body>
<h1>Bem vindo</h1>
<p>Uma magnífica página inicial, não é?</p>
</body>
</html>
package primavera.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/")
public String home() {
return "index";
}
}
Códigos de mensagens
Para utilizar a internacionalização, substituimos as frases que queremos ser multi-idiomas por códigos de mensagens:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><spring:message code="home.title"/></title>
</head>
<body>
<h1><spring:message code="home.title"/></h1>
<p><spring:message code="home.intro"/></p>
</body>
</html>
Crie, na área de Pacotes de Códigos-fonte os seguintes arquivos de propriedades, no NetBeans, eles ficarão aparecendo no <pacote default>.
Observe que os nomes dos arquivos contém a identificação do idioma (pt é português e en é inglês). Com esses dois arquivos, a nossa aplicação estará disponível em português e inglês.
home.title=Bem vindo
home.intro=Uma magnífica página inicial, não é?
home.title=Welcome
home.intro=This is a magnificent home page, isn't it?
Configuração de internacionalização
É preciso uma configuração mínima, para que a internacionalização funcione:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"primavera.dao", "primavera.controller"})
public class AppConfig {
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
}
Idioma padrão
O idioma padrão, caso não seja requisitado nenhum dos disponíveis é o padrão do sistema operacional. Se o seu sistema for em português, então o Spring usará português como o padrão.
O código até agora
Você pode baixar o código até esse ponto aqui.