Construindo um aplicativo MVC com Spring Framework: um tutorial para iniciantes
Publicados: 2022-03-11Diz-se frequentemente que Java é muito complicado e leva muito tempo para construir aplicativos simples. No entanto, o Java fornece uma plataforma estável com um ecossistema muito maduro ao seu redor, o que o torna uma ótima opção para o desenvolvimento de software robusto.
O Spring Framework, um dos muitos frameworks poderosos no ecossistema Java, vem com uma coleção de modelos de programação e configuração com o objetivo de simplificar o desenvolvimento de aplicativos de desempenho e testáveis em Java.
Neste tutorial, aceitaremos o desafio de construir uma aplicação simples que funcionará como um banco de dados de desenvolvedores de software utilizando Spring Framework e a Java Persistence API (JPA).
A aplicação segue uma arquitetura MVC padrão. Ele terá um controlador (classe ContractsController), visualizações (baseadas em modelos Thymeleaf) e um modelo (um objeto de mapa Java). Para simplificar, usaremos um banco de dados na memória por trás do JPA para persistir os dados enquanto o aplicativo estiver em execução.
Introdução ao tutorial do Spring Framework
Para construir um aplicativo baseado em Spring, precisaremos usar uma das seguintes ferramentas de construção:
- Especialista
- Gradle
Neste tutorial, usaremos o Maven. Se você não estiver familiarizado com nenhuma dessas ferramentas, uma maneira fácil de começar é baixar o Spring Tool Suite. A suíte é dedicada ao Spring Framework e vem com seu próprio IDE baseado em Eclipse.
No Spring Tool Suite, criamos um novo projeto selecionando “Spring Starter Project” no menu “File > New”.
Assim que um novo projeto for criado, precisaremos editar o arquivo de configuração do Maven, “ pom.xml ”, e adicionar as seguintes dependências:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> </dependency>
Essas dependências listadas carregarão Spring Boot Web, Thymeleaf, JPA e H2 (que servirão como nosso banco de dados na memória). Todas as bibliotecas necessárias serão extraídas automaticamente.
Classes de entidade
Para poder armazenar informações sobre desenvolvedores e suas habilidades, precisaremos definir duas classes de entidade: “ Desenvolvedor ” e “ Habilidade ”.
Ambos são definidos como classes Java simples com algumas anotações. Ao adicionar “@Entity” antes das classes, estamos disponibilizando suas instâncias para JPA. Isso tornará mais fácil armazenar e recuperar instâncias do armazenamento de dados persistentes quando necessário. Além disso, as anotações “@Id” e “@GeneratedValue” nos permitem indicar o campo de ID único para a entidade e ter seu valor gerado automaticamente quando armazenado no banco de dados.
Como um desenvolvedor pode ter muitas habilidades, podemos definir um relacionamento simples de muitos para muitos usando a anotação “@ManyToMany”.
Desenvolvedor
@Entity public class Developer { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String firstName; private String lastName; private String email; @ManyToMany private List<Skill> skills; public Developer() { super(); } public Developer(String firstName, String lastName, String email, List<Skill> skills) { super(); this.firstName = firstName; this.lastName = lastName; this.email = email; this.skills = skills; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<Skill> getSkills() { return skills; } public void setSkills(List<Skill> skills) { this.skills = skills; } public boolean hasSkill(Skill skill) { for (Skill containedSkill: getSkills()) { if (containedSkill.getId() == skill.getId()) { return true; } } return false; } }
Habilidade
@Entity public class Skill { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String label; private String description; public Skill() { super(); } public Skill(String label, String description) { super(); this.label = label; this.description = description; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
Repositórios
Com JPA podemos definir uma interface DeveloperRepository muito útil e uma interface SkillRepository, que permitem operações CRUD fáceis. Essas interfaces nos permitirão acessar desenvolvedores e habilidades armazenados por meio de chamadas de método simples, como:
- “respository.findAll()”: retorna todos os desenvolvedores
- “repository.findOne(id)”: retorna o desenvolvedor com o ID fornecido
Para criar essas interfaces, tudo o que precisamos fazer é estender a interface CrudRepository.
Repositório do desenvolvedor
public interface DeveloperRepository extends CrudRepository<Developer, Long> { }
Repositório de Habilidades
public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }
Funcionalidade para o método adicional “ findByLabel “declarado aqui será fornecido automaticamente pelo JPA.
Controlador
Em seguida, podemos trabalhar no controlador para esta aplicação. O controlador mapeará os URIs de solicitação para visualizar os modelos e executará todo o processamento necessário entre eles.
@Controller public class DevelopersController { @Autowired DeveloperRepository repository; @Autowired SkillRepository skillRepository; @RequestMapping("/developer/{id}") public String developer(@PathVariable Long id, Model model) { model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "developer"; } @RequestMapping(value="/developers",method=RequestMethod.GET) public String developersList(Model model) { model.addAttribute("developers", repository.findAll()); return "developers"; } @RequestMapping(value="/developers",method=RequestMethod.POST) public String developersAdd(@RequestParam String email, @RequestParam String firstName, @RequestParam String lastName, Model model) { Developer newDeveloper = new Developer(); newDeveloper.setEmail(email); newDeveloper.setFirstName(firstName); newDeveloper.setLastName(lastName); repository.save(newDeveloper); model.addAttribute("developer", newDeveloper); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + newDeveloper.getId(); } @RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST) public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) { Skill skill = skillRepository.findOne(skillId); Developer developer = repository.findOne(id); if (developer != null) { if (!developer.hasSkill(skill)) { developer.getSkills().add(skill); } repository.save(developer); model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + developer.getId(); } model.addAttribute("developers", repository.findAll()); return "redirect:/developers"; } }
O mapeamento de URIs para métodos é feito por meio de anotações simples “@RequestMapping”. Nesse caso, cada método do controlador é mapeado para um URI.

O parâmetro de modelo desses métodos permite que os dados sejam passados para a visualização. Em essência, esses são mapas simples de chaves para valores.
Cada método do controlador retorna o nome do modelo Thymeleaf a ser usado como visualização ou uma URL em um padrão específico (“redirect:
Dentro do controlador, as anotações “@Autowired” atribuem automaticamente uma instância válida de nosso repositório definido no campo correspondente. Isso permite o acesso a dados relevantes de dentro do controlador sem ter que lidar com muito código clichê.
Visualizações
Por fim, precisamos definir alguns templates para as visualizações a serem geradas. Para isso, estamos usando o Thymeleaf, um mecanismo de modelagem simples. O modelo que usamos nos métodos do controlador está disponível diretamente dentro dos templates, ou seja, quando inserimos um contrato na chave “ contrato ” de um modelo, poderemos acessar o campo de nome como “contract.name” de dentro do template.
Thymeleaf contém alguns elementos e atributos especiais que controlam a geração de HTML. Eles são muito intuitivos e diretos. Por exemplo, para preencher o conteúdo de um elemento span com o nome de uma habilidade, tudo o que você precisa fazer é definir o seguinte atributo (assumindo que a chave “ habilidade ” esteja definida no modelo):
<span th:text="${skill.label}"></span>
Da mesma forma para definir o atributo “ href ” de um elemento âncora, o atributo especial “ th:href ” pode ser usado.
Em nossa aplicação, precisaremos de dois modelos simples. Para maior clareza, vamos pular todos os atributos de estilo e classe (ou seja, os de Bootstrap) aqui no código do modelo incorporado.
Lista de desenvolvedores
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developers database</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developers</h1> <table> <tr> <th>Name</th> <th>Skills</th> <th></th> </tr> <tr th:each="developer : ${developers}"> <td th:text="${developer.firstName + ' ' + developer.lastName}"></td> <td> <span th:each="skill,iterStat : ${developer.skills}"> <span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block> </span> </td> <td> <a th:href="@{/developer/{id}(id=${developer.id})}">view</a> </td> </tr> </table> <hr/> <form th:action="@{/developers}" method="post" enctype="multipart/form-data"> <div> First name: <input name="firstName" /> </div> <div> Last name: <input name="lastName" /> </div> <div> Email: <input name="email" /> </div> <div> <input type="submit" value="Create developer" name="button"/> </div> </form> </body> </html>
Detalhes do desenvolvedor
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developer</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developer</h1> Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/> Email: <span th:text="${developer.email}" /><br/> Skills: <span th:each="skill : ${developer.skills}"> <br/> <span th:text="${skill.label}" /> - <span th:text="${skill.description}" /> </span> <form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" > <select name="skillId"> <option th:each="skill : ${skills}" th:value="${skill.id}" th:text="${skill.description}">Skill</option> </select> <input type="submit" value="Add skill"/> </form> </body> </html>
Executando o Servidor
Spring contém um módulo de inicialização. Isso nos permite iniciar o servidor facilmente a partir da linha de comando como um aplicativo Java de linha de comando:
@SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Como estamos usando um banco de dados na memória, faz sentido inicializar o banco de dados com alguns dados predefinidos na inicialização. Dessa forma, teremos pelo menos alguns dados no banco de dados quando o servidor estiver funcionando.
@Override public void run(String... args) throws Exception { Skill javascript = new Skill("javascript", "Javascript language skill"); Skill ruby = new Skill("ruby", "Ruby language skill"); Skill emberjs = new Skill("emberjs", "Emberjs framework"); Skill angularjs = new Skill("angularjs", "Angularjs framework"); skillRepository.save(javascript); skillRepository.save(ruby); skillRepository.save(emberjs); skillRepository.save(angularjs); List<Developer> developers = new LinkedList<Developer>(); developers.add(new Developer("John", "Smith", "[email protected]", Arrays.asList(new Skill[] { javascript, ruby }))); developers.add(new Developer("Mark", "Johnson", "[email protected]", Arrays.asList(new Skill[] { emberjs, ruby }))); developers.add(new Developer("Michael", "Williams", "[email protected]", Arrays.asList(new Skill[] { angularjs, ruby }))); developers.add(new Developer("Fred", "Miller", "[email protected]", Arrays.asList(new Skill[] { emberjs, angularjs, javascript }))); developers.add(new Developer("Bob", "Brown", "[email protected]", Arrays.asList(new Skill[] { emberjs }))); developerRepository.save(developers); }
Conclusão
Spring é um framework versátil que permite construir aplicações MVC. Construir um aplicativo simples com Spring é rápido e transparente. O aplicativo também pode ser integrado a um banco de dados facilmente usando JPA.
O código-fonte de todo este projeto está disponível no GitHub.