Construindo um aplicativo MVC com Spring Framework: um tutorial para iniciantes

Publicados: 2022-03-11

Diz-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.

Estrutura da Primavera

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”.

Tutorial do Spring Framework

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: ") para redirecionar. Por exemplo, os métodos “developer” e “_developersList_” retornam o nome de um modelo, enquanto “developersAdd” e “developersAddSkill” retornam URLs para redirecionar.

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/>&nbsp;&nbsp;<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.