بناء تطبيق MVC مع إطار الربيع: دروس للمبتدئين

نشرت: 2022-03-11

غالبًا ما يُقال إن Java معقدة للغاية وتستغرق وقتًا طويلاً لإنشاء تطبيقات بسيطة. ومع ذلك ، توفر Java منصة مستقرة مع نظام بيئي ناضج للغاية حولها ، مما يجعلها خيارًا رائعًا لتطوير برامج قوية.

يأتي Spring Framework ، أحد الأطر القوية العديدة في نظام Java البيئي ، مع مجموعة من نماذج البرمجة والتكوين بهدف تبسيط تطوير التطبيقات عالية الأداء والقابلة للاختبار في Java.

إطار الربيع

في هذا البرنامج التعليمي ، سنواجه التحدي المتمثل في إنشاء تطبيق بسيط يعمل كقاعدة بيانات لمطوري البرامج باستخدام Spring Framework وواجهة برمجة تطبيقات جافا (JPA).

يتبع التطبيق بنية MVC قياسية. سيكون لها وحدة تحكم (فئة ContractsController) ، وجهات نظر (بناءً على قوالب Thymeleaf) ، ونموذج (كائن خريطة Java). من أجل البساطة ، سنستخدم قاعدة بيانات في الذاكرة خلف JPA لاستمرار البيانات أثناء تشغيل التطبيق.

الشروع في العمل مع Spring Framework التعليمي

لإنشاء تطبيق يستند إلى Spring ، سنحتاج إلى استخدام إحدى أدوات البناء التالية:

  • مخضرم
  • جرادل

في هذا البرنامج التعليمي ، سوف نستخدم Maven. إذا لم تكن معتادًا على أي من هذه الأدوات ، فإن الطريقة السهلة للبدء هي تنزيل Spring Tool Suite. الجناح مخصص لـ Spring Framework ، ويأتي مع بيئة تطوير متكاملة تعتمد على 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.

مراقب

بعد ذلك ، يمكننا العمل على وحدة التحكم لهذا التطبيق. ستطلب وحدة التحكم تعيين محددات مواقع المعلومات (URIs) لعرض القوالب وإجراء جميع العمليات اللازمة بينهما.

 @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"; } }

يتم تعيين URIs للطرق من خلال التعليقات التوضيحية "RequestMapping" البسيطة. في هذه الحالة ، يتم تعيين كل طريقة لوحدة التحكم إلى URI.

تسمح معلمة النموذج لهذه الطرق بتمرير البيانات إلى طريقة العرض. في جوهرها ، هذه خرائط بسيطة لمفاتيح القيم.

تعرض كل طريقة تحكم إما اسم نموذج Thymeleaf لاستخدامه كعرض ، أو عنوان URL في نمط معين ("إعادة التوجيه: ”) لإعادة التوجيه إلى. على سبيل المثال ، تقوم الطريقتان "developer" و "_developersList_" بإرجاع اسم القالب ، بينما تقوم "developerAdd" و "developerAddSkill" بإرجاع عناوين URL لإعادة التوجيه إليها.

داخل وحدة التحكم ، تقوم التعليقات التوضيحية "Autowired" تلقائيًا بتعيين مثيل صالح لمستودعنا المحدد في الحقل المقابل. يتيح ذلك الوصول إلى البيانات ذات الصلة من داخل وحدة التحكم دون الحاجة إلى التعامل مع الكثير من التعليمات البرمجية المعيارية.

الآراء

أخيرًا ، نحتاج إلى تحديد بعض القوالب للمشاهدات التي سيتم إنشاؤها. لهذا نحن نستخدم Thymeleaf ، محرك قالب بسيط. النموذج الذي استخدمناه في طرق التحكم متاح مباشرة داخل القوالب ، أي عندما ندخل عقدًا في مفتاح " عقد " في نموذج ، سنكون قادرين على الوصول إلى حقل الاسم باسم "Contract.name" من داخل النموذج.

يحتوي Thymeleaf على بعض العناصر والسمات الخاصة التي تتحكم في إنشاء HTML. إنها بديهية للغاية ومباشرة. على سبيل المثال ، لملء محتويات عنصر امتداد باسم مهارة ما ، كل ما عليك فعله هو تحديد السمة التالية (على افتراض أن مفتاح " المهارة " محدد في النموذج):

 <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/>&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>

تشغيل الخادم

الربيع يحتوي على وحدة التمهيد. يتيح لنا ذلك بدء تشغيل الخادم بسهولة من سطر الأوامر كتطبيق 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); }

خاتمة

الربيع هو إطار متعدد الاستخدامات يسمح ببناء تطبيقات MVC. يعد إنشاء تطبيق بسيط باستخدام Spring أمرًا سريعًا وشفافًا. يمكن أيضًا دمج التطبيق مع قاعدة بيانات بسهولة باستخدام JPA.

الكود المصدري لهذا المشروع بأكمله متاح على GitHub.