Introdução ao Spring Framework

16/04/2018

Preparação

Novo projeto de aplicação web

  1. Crie um novo projeto de Aplicação Web pelo NetBeans.
  2. Nome do projeto: “SpringWebApp
  3. Servidor: GlassFish
  4. Frameworks: escolha Spring Web MVC, na configuração, escolha a versão 4.0.1 e deixe marcada a opção Incluir JSTL.
  5. 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:

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:

  1. Criar um método add(Car) na classe CarDao.
  2. Criar dois métodos carAdd() e carAdd(Car) à classe CarController.
  3. 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:

  1. Vamos adicionar um identificador à entidade Car. Para que o carro a ser editado possa ser encontrado facilmente, é preciso adicionar um campo id.

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";
    }
}

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:

  1. Português (Brasil)
  2. Português
  3. Inglês (Americano)
  4. 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.