Создание приложения MVC с помощью Spring Framework: руководство для начинающих
Опубликовано: 2022-03-11Часто говорят, что Java слишком сложна и требует слишком много времени для создания простых приложений. Тем не менее, Java представляет собой стабильную платформу с очень зрелой экосистемой вокруг нее, что делает ее прекрасным вариантом для разработки надежного программного обеспечения.
Spring Framework, одна из многих мощных платформ в экосистеме Java, поставляется с набором моделей программирования и конфигурации с целью упростить разработку производительных и тестируемых приложений на Java.
В этом руководстве мы возьмем на себя задачу создания простого приложения, которое будет действовать как база данных разработчиков программного обеспечения с использованием Spring Framework и Java Persistence API (JPA).
Приложение следует стандартной архитектуре MVC. Он будет иметь контроллер (класс ContractsController), представления (на основе шаблонов Thymeleaf) и модель (объект карты Java). Для простоты мы будем использовать базу данных в памяти за JPA для сохранения данных во время работы приложения.
Руководство по началу работы с Spring Framework
Чтобы создать приложение на основе Spring, нам понадобится один из следующих инструментов сборки:
- Мавен
- Грейдл
В этом уроке мы будем использовать Maven. Если вы не знакомы ни с одним из этих инструментов, простой способ начать работу — загрузить Spring Tool Suite. Пакет предназначен для Spring Framework и поставляется с собственной IDE на основе Eclipse.
В Spring Tool Suite мы создаем новый проект, выбрав «Spring Starter Project» в меню «Файл > Создать».
После создания нового проекта нам нужно будет отредактировать файл конфигурации Maven « pom.xml » и добавить следующие зависимости:
<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>
Эти перечисленные зависимости будут загружать Spring Boot Web, Thymeleaf, JPA и H2 (который будет служить нашей базой данных в памяти). Все необходимые библиотеки будут подтянуты автоматически.
Классы сущностей
Чтобы иметь возможность хранить информацию о разработчиках и их навыках, нам потребуется определить два класса сущностей: « Разработчик » и « Навык ».
Оба они определены как простые классы Java с некоторыми аннотациями. Добавляя «@Entity» перед классами, мы делаем их экземпляры доступными для JPA. Это упростит хранение и извлечение экземпляров из постоянного хранилища данных при необходимости. Кроме того, аннотации «@Id» и «@GeneratedValue» позволяют нам указать поле уникального идентификатора для объекта и автоматически сгенерировать его значение при сохранении в базе данных.
Поскольку у разработчика может быть много навыков, мы можем определить простое отношение «многие ко многим», используя аннотацию «@ManyToMany».
Разработчик
@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; } }
Навык
@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; } }
Репозитории
С помощью JPA мы можем определить очень полезный интерфейс DeveloperRepository и интерфейс SkillRepository, которые позволяют легко выполнять операции CRUD. Эти интерфейсы позволят нам получить доступ к сохраненным разработчикам и навыкам с помощью простых вызовов методов, таких как:
- «respository.findAll()»: возвращает всех разработчиков
- «repository.findOne(id)»: возвращает разработчика с заданным идентификатором
Чтобы создать эти интерфейсы, все, что нам нужно сделать, это расширить интерфейс CrudRepository.
Репозиторий разработчиков
public interface DeveloperRepository extends CrudRepository<Developer, Long> { }
Хранилище навыков
public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }
Функциональность для объявленного здесь дополнительного метода « findByLabel » будет автоматически предоставлена JPA.
Контроллер
Далее мы можем работать над контроллером для этого приложения. Контроллер будет отображать URI запроса для просмотра шаблонов и выполнять всю необходимую промежуточную обработку.
@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"; } }
Сопоставление URI с методами выполняется с помощью простых аннотаций «@RequestMapping». В этом случае каждый метод контроллера сопоставляется с URI.

Параметр модели этих методов позволяет передавать данные в представление. По сути, это простые карты ключей к значениям.
Каждый метод контроллера либо возвращает имя шаблона Thymeleaf, который будет использоваться в качестве представления, либо URL-адрес в определенном шаблоне («перенаправление:
В контроллере аннотации «@Autowired» автоматически назначают действительный экземпляр нашего определенного репозитория в соответствующем поле. Это позволяет получить доступ к соответствующим данным внутри контроллера без необходимости иметь дело с большим количеством стандартного кода.
Просмотры
Наконец, нам нужно определить некоторые шаблоны для генерируемых представлений. Для этого мы используем Thymeleaf, простой шаблонизатор. Модель, которую мы использовали в методах контроллера, доступна непосредственно в шаблонах, т. е. когда мы вводим контракт в ключ « contract » в модели, мы сможем получить доступ к полю имени как «contract.name» из шаблона.
Thymeleaf содержит некоторые специальные элементы и атрибуты, управляющие генерацией HTML. Они очень интуитивно понятны и просты. Например, чтобы заполнить содержимое элемента span названием навыка, все, что вам нужно сделать, это определить следующий атрибут (при условии, что ключ « skill » определен в модели):
<span th:text="${skill.label}"></span>
Аналогично для установки атрибута « href » элемента привязки можно использовать специальный атрибут « th:href ».
В нашем приложении нам понадобятся два простых шаблона. Для ясности мы пропустим здесь все атрибуты стиля и класса (а именно атрибуты Bootstrap) во встроенном коде шаблона.
Список разработчиков
<!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>
Сведения о разработчике
<!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>
Запуск сервера
Spring содержит загрузочный модуль. Это позволяет нам легко запускать сервер из командной строки как Java-приложение командной строки:
@SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Поскольку мы используем базу данных в памяти, имеет смысл загрузить базу данных с некоторыми предопределенными данными при запуске. Таким образом, у нас будут хотя бы некоторые данные в базе данных, когда сервер запущен и работает.
@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); }
Заключение
Spring — это универсальная среда, которая позволяет создавать приложения MVC. Создание простого приложения с помощью Spring происходит быстро и прозрачно. Приложение также можно легко интегрировать с базой данных с помощью JPA.
Исходный код всего этого проекта доступен на GitHub.