Creazione di un'applicazione MVC con Spring Framework: un tutorial per principianti
Pubblicato: 2022-03-11Si dice spesso che Java sia troppo complicato e richieda troppo tempo per creare applicazioni semplici. Tuttavia, Java fornisce una piattaforma stabile con un ecosistema molto maturo che la rende un'opzione meravigliosa per lo sviluppo di software robusto.
Spring Framework, uno dei tanti potenti framework nell'ecosistema Java, viene fornito con una raccolta di modelli di programmazione e configurazione con l'obiettivo di semplificare lo sviluppo di applicazioni performanti e testabili in Java.
In questo tutorial, affronteremo la sfida di creare una semplice applicazione che fungerà da database di sviluppatori software utilizzando Spring Framework e Java Persistence API (JPA).
L'applicazione segue un'architettura MVC standard. Avrà un controller (classe ContractsController), viste (basate su modelli Thymeleaf) e un modello (un oggetto mappa Java). Per semplicità, utilizzeremo un database in memoria dietro JPA per rendere persistenti i dati mentre l'applicazione è in esecuzione.
Introduzione al tutorial di Spring Framework
Per creare un'applicazione basata su Spring, dovremo utilizzare uno dei seguenti strumenti di compilazione:
- Esperto di
- Grad
In questo tutorial useremo Maven. Se non hai familiarità con nessuno di questi strumenti, un modo semplice per iniziare è scaricare Spring Tool Suite. La suite è dedicata a Spring Framework e viene fornita con il proprio IDE basato su Eclipse.
In Spring Tool Suite, creiamo un nuovo progetto selezionando "Progetto Spring Starter" dal menu "File > Nuovo".
Una volta creato un nuovo progetto, dovremo modificare il file di configurazione di Maven, " pom.xml ", e aggiungere le seguenti dipendenze:
<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>
Queste dipendenze elencate caricheranno Spring Boot Web, Thymeleaf, JPA e H2 (che fungeranno da database in memoria). Tutte le librerie necessarie verranno estratte automaticamente.
Classi di entità
Per poter archiviare informazioni sugli sviluppatori e sulle loro competenze, dovremo definire due classi di entità: " Sviluppatore " e " Abilità ".
Entrambi sono definiti come semplici classi Java con alcune annotazioni. Aggiungendo "@Entity" prima delle classi, rendiamo disponibili le loro istanze a JPA. Ciò semplificherà l'archiviazione e il recupero delle istanze dall'archivio dati persistente quando necessario. Inoltre, le annotazioni "@Id" e "@GeneratedValue" ci consentono di indicare il campo ID univoco per l'entità e di generare automaticamente il suo valore quando archiviato nel database.
Poiché uno sviluppatore può avere molte abilità, possiamo definire una semplice relazione molti-a-molti usando l'annotazione "@ManyToMany".
Sviluppatore
@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; } }
Abilità
@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; } }
Repository
Con JPA possiamo definire un'interfaccia DeveloperRepository molto utile e un'interfaccia SkillRepository, che consentono facili operazioni CRUD. Queste interfacce ci consentiranno di accedere agli sviluppatori e alle competenze archiviate tramite semplici chiamate di metodo, come ad esempio:
- “repository.findAll()”: restituisce tutti gli sviluppatori
- "repository.findOne(id)": restituisce lo sviluppatore con l'ID specificato
Per creare queste interfacce, tutto ciò che dobbiamo fare è estendere l'interfaccia di CrudRepository.
Archivio degli sviluppatori
public interface DeveloperRepository extends CrudRepository<Developer, Long> { }
Archivio delle abilità
public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }
Le funzionalità per il metodo aggiuntivo “ findByLabel ” qui dichiarato verranno fornite automaticamente da JPA.
Controllore
Successivamente, possiamo lavorare sul controller per questa applicazione. Il controller mapperà gli URI di richiesta per visualizzare i modelli ed eseguire tutte le elaborazioni necessarie nel frattempo.
@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"; } }
La mappatura degli URI sui metodi viene eseguita tramite semplici annotazioni "@RequestMapping". In questo caso, ogni metodo del controller è mappato su un URI.

Il parametro del modello di questi metodi consente di passare i dati alla vista. In sostanza, si tratta di semplici mappe di chiavi di valori.
Ciascun metodo del controller restituisce il nome del modello Thymeleaf da utilizzare come vista o un URL in uno schema specifico ("reindirizzamento:
All'interno del controller, le annotazioni "@Autowired" assegnano automaticamente un'istanza valida del nostro repository definito nel campo corrispondente. Ciò consente l'accesso ai dati rilevanti dall'interno del controller senza dover gestire molto codice standard.
Visualizzazioni
Infine, dobbiamo definire alcuni modelli per le viste da generare. Per questo stiamo usando Thymeleaf, un semplice motore di template. Il modello che abbiamo utilizzato nei metodi del controller è disponibile direttamente all'interno dei modelli, ovvero quando inseriamo un contratto in chiave " contratto " in un modello, saremo in grado di accedere al campo del nome come "nome contratto" dall'interno del modello.
Thymeleaf contiene alcuni elementi e attributi speciali che controllano la generazione di HTML. Sono molto intuitivi e diretti. Ad esempio, per popolare il contenuto di un elemento span con il nome di una competenza, è sufficiente definire il seguente attributo (supponendo che la chiave " competenza " sia definita nel modello):
<span th:text="${skill.label}"></span>
Analogamente per impostare l'attributo “ href ” di un elemento anchor, è possibile utilizzare l'attributo speciale “ th:href ”.
Nella nostra applicazione, avremo bisogno di due semplici modelli. Per chiarezza, salteremo tutti gli attributi di stile e classe (in particolare quelli Bootstrap) qui nel codice del modello incorporato.
Elenco degli sviluppatori
<!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>
Dettagli dello sviluppatore
<!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>
Esecuzione del server
Spring contiene un modulo di avvio. Questo ci consente di avviare facilmente il server dalla riga di comando come applicazione Java della riga di comando:
@SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Poiché stiamo utilizzando un database in memoria, ha senso avviare il database con alcuni dati predefiniti all'avvio. In questo modo avremo almeno alcuni dati nel database quando il server è attivo e funzionante.
@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); }
Conclusione
Spring è un framework versatile che consente di creare applicazioni MVC. Realizzare una semplice applicazione con Spring è veloce e trasparente. L'applicazione può anche essere integrata facilmente con un database utilizzando JPA.
Il codice sorgente di questo intero progetto è disponibile su GitHub.