Tworzenie aplikacji MVC za pomocą Spring Framework: samouczek dla początkujących
Opublikowany: 2022-03-11Często mówi się, że Java jest zbyt skomplikowana, a tworzenie prostych aplikacji zajmuje zbyt dużo czasu. Niemniej jednak Java zapewnia stabilną platformę z bardzo dojrzałym ekosystemem wokół niej, co czyni ją wspaniałą opcją do tworzenia solidnego oprogramowania.
Spring Framework, jeden z wielu potężnych frameworków w ekosystemie Java, zawiera zbiór modeli programowania i konfiguracji, których celem jest uproszczenie tworzenia wydajnych i testowalnych aplikacji w Javie.
W tym samouczku podejmiemy wyzwanie zbudowania prostej aplikacji, która będzie działać jako baza danych programistów korzystających ze Spring Framework i Java Persistence API (JPA).
Aplikacja jest zgodna ze standardową architekturą MVC. Będzie miał kontroler (klasa ContractsController), widoki (oparte na szablonach Thymeleaf) i model (obiekt mapy Java). Dla uproszczenia użyjemy bazy danych w pamięci za JPA, aby utrwalić dane podczas działania aplikacji.
Pierwsze kroki z samouczkiem Spring Framework
Aby zbudować aplikację opartą na Springu, będziemy musieli użyć jednego z następujących narzędzi do budowania:
- Maven
- Gradle
W tym samouczku użyjemy Mavena. Jeśli nie znasz żadnego z tych narzędzi, łatwym sposobem na rozpoczęcie pracy jest pobranie pakietu Spring Tool Suite. Pakiet jest dedykowany dla Spring Framework i zawiera własne IDE oparte na Eclipse.
W Spring Tool Suite tworzymy nowy projekt wybierając „Spring Starter Project” z menu „File > New”.
Po utworzeniu nowego projektu będziemy musieli edytować plik konfiguracyjny Mavena „ pom.xml ” i dodać następujące zależności:
<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>
Te wymienione zależności załadują Spring Boot Web, Thymeleaf, JPA i H2 (które będą służyć jako nasza baza danych w pamięci). Wszystkie niezbędne biblioteki zostaną ściągnięte automatycznie.
Klasy jednostek
Aby móc przechowywać informacje o programistach i ich umiejętnościach, musimy zdefiniować dwie klasy encji: „ Deweloper ” i „ Umiejętność ”.
Oba są zdefiniowane jako zwykłe klasy Java z pewnymi adnotacjami. Dodając „@Entity” przed zajęciami, udostępniamy ich instancje dla JPA. Ułatwi to przechowywanie i pobieranie instancji z trwałego magazynu danych w razie potrzeby. Dodatkowo adnotacje „@Id” i „@GeneratedValue” pozwalają nam wskazać pole unikalnego identyfikatora dla encji i automatycznie wygenerować jego wartość podczas przechowywania w bazie danych.
Ponieważ programista może mieć wiele umiejętności, możemy zdefiniować prostą relację wiele-do-wielu za pomocą adnotacji „@ManyToMany”.
Deweloper
@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; } }
Umiejętność
@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; } }
Repozytoria
Dzięki JPA możemy zdefiniować bardzo przydatny interfejs DeveloperRepository oraz SkillRepository, które pozwalają na łatwe operacje CRUD. Te interfejsy pozwolą nam uzyskać dostęp do zapisanych programistów i umiejętności za pomocą prostych wywołań metod, takich jak:
- „respository.findAll()”: zwraca wszystkich programistów
- „repository.findOne(id)”: zwraca programistę o podanym identyfikatorze
Aby stworzyć te interfejsy, wystarczy rozszerzyć interfejs CrudRepository.
Repozytorium programistów
public interface DeveloperRepository extends CrudRepository<Developer, Long> { }
Repozytorium umiejętności
public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }
Funkcjonalność dodatkowej metody „ findByLabel ” zadeklarowanej w tym miejscu zostanie automatycznie udostępniona przez JPA.
Kontroler
Następnie możemy popracować nad kontrolerem dla tej aplikacji. Kontroler zmapuje identyfikatory URI żądania, aby wyświetlić szablony i wykonać wszystkie niezbędne przetwarzanie w międzyczasie.
@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"; } }
Mapowanie identyfikatorów URI do metod odbywa się za pomocą prostych adnotacji „@RequestMapping”. W takim przypadku każda metoda kontrolera jest mapowana na identyfikator URI.

Parametr modelu tych metod umożliwia przekazywanie danych do widoku. W istocie są to proste mapy kluczy do wartości.
Każda metoda kontrolera zwraca albo nazwę szablonu Thymeleaf, który ma być używany jako widok, albo adres URL w określonym wzorcu („przekierowanie:
W kontrolerze adnotacje „@Autowired” automatycznie przypisują prawidłową instancję naszego zdefiniowanego repozytorium w odpowiednim polu. Umożliwia to dostęp do odpowiednich danych z poziomu sterownika bez konieczności zajmowania się dużą ilością kodu wzorcowego.
Wyświetlenia
Na koniec musimy zdefiniować szablony do generowania widoków. W tym celu używamy Thymeleaf, prostego silnika szablonów. Model, który zastosowaliśmy w metodach kontrolera jest dostępny bezpośrednio w szablonach, tzn. gdy wprowadzimy umowę do klucza „ umowa ” w modelu, będziemy mogli uzyskać dostęp do pola nazwy jako „nazwa.kontraktu” z poziomu szablonu.
Thymeleaf zawiera kilka specjalnych elementów i atrybutów, które sterują generowaniem kodu HTML. Są bardzo intuicyjne i proste. Na przykład, aby wypełnić zawartość elementu span nazwą umiejętności, wystarczy zdefiniować następujący atrybut (zakładając, że klucz „ umiejętność ” jest zdefiniowany w modelu):
<span th:text="${skill.label}"></span>
Podobnie do ustawienia atrybutu „ href ” elementu kotwicy, można użyć specjalnego atrybutu „ th:href ”.
W naszej aplikacji będziemy potrzebować dwóch prostych szablonów. Dla jasności pominiemy tutaj wszystkie atrybuty stylu i klasy (a mianowicie Bootstrap) w osadzonym kodzie szablonu.
Lista programistów
<!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>
Szczegóły programisty
<!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>
Uruchamianie serwera
Spring zawiera moduł rozruchowy. Dzięki temu możemy łatwo uruchomić serwer z wiersza poleceń jako aplikację Java wiersza poleceń:
@SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Ponieważ używamy bazy danych w pamięci, sensowne jest załadowanie bazy danych z niektórymi predefiniowanymi danymi podczas uruchamiania. W ten sposób będziemy mieć przynajmniej część danych w bazie danych, gdy serwer będzie działał.
@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); }
Wniosek
Spring to wszechstronny framework, który umożliwia budowanie aplikacji MVC. Budowanie prostej aplikacji za pomocą Springa jest szybkie i przejrzyste. Aplikację można również łatwo zintegrować z bazą danych za pomocą JPA.
Kod źródłowy całego projektu jest dostępny na GitHub.