14. [TD]: عرض الويب لطبقة [الأعمال]
الكلمات المفتاحية: بنية متعددة المستويات، Spring، حقن التبعية، خدمة الويب / JSON، العميل / الخادم.
لنعد إلى البنية الحالية لتطبيق TD:
![]() |
سنطور هذه البنية لتصبح كما يلي:
![]() |
من أجل عرض واجهة [IMetier] لطبقة الأعمال على الويب. للقيام بذلك، سنتبع المنهجية الموضحة في القسم 13.5.
14.1. الدعم
![]() |
يمكن العثور على مشاريع هذا الفصل في المجلد [support / chap-14].
14.2. مشروع Eclipse الخاص بطبقة [الأعمال]
![]() |
![]() |
14.2.1. تكوين Maven
مشروع طبقة [الأعمال] هو مشروع Maven تم تكوينه بواسطة ملف [pom.xml] التالي:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
<!-- dependencies -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- layer [DAO] -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<!-- use UTF-8 for everything -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- الأسطر 18–22: التبعية لطبقة [DAO] التي تم إنشاؤها في الفقرة 12؛
- الأسطر 23–34: التبعيات المطلوبة للاختبار؛
14.2.2. تكوين Spring
![]() |
مشروع طبقة [الأعمال] هو مشروع Spring تم تكوينه بواسطة ملف [MetierConfig] التالي:
package elections.metier.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import elections.dao.config.DaoConfig;
@Import({ DaoConfig.class })
@ComponentScan({ "elections.metier.service" })
public class MetierConfig {
}
- نحن لا نستخدم التعليق التوضيحي [@Configuration] هنا، والذي من شأنه أن يجعل الفئة فئة تكوين Spring. إن وجود التعليقات التوضيحية [@Import] و [@ComponentScan] يجعلها تلقائيًا فئة تكوين؛
- السطر 8: نقوم باستيراد ملف التكوين من طبقة [DAO]. وبذلك يصبح بإمكاننا الوصول إلى جميع الفاصوليا المحددة في هذا الملف؛
- السطر 9: توجد مكونات Spring الأخرى في المجلد [elections.metier.service]؛
14.2.3. تنفيذ طبقة [business]
![]() |
تنفيذ طبقة [الأعمال] هو ذلك المحدد في القسم 8.5.
14.2.4. اختبار طبقة [الأعمال]
![]() |
فئة الاختبار هي تلك الموصوفة في القسم 8.6.
المهمة: قم بتنفيذ مشروع طبقة [الأعمال] واجتاز اختبار الوحدة الخاص به. قم بإنشاء أرشيف الطبقة في مستودع Maven المحلي (Run As / Maven / Install).
14.3. مشروع Eclipse لطبقة [الويب]
![]() |
طبقة الويب هي طبقة Spring MVC:
![]() |
يتميز مشروع Eclipse بالهيكل التالي:
![]() | ![]() |
- [Boot.java] هي الفئة التي تطلق خدمة الويب؛
- [WebConfig.java] هي فئة تكوين خدمة الويب؛
- [Response.java] هي الاستجابة التي تولدها عناوين URL المختلفة لخدمة الويب؛
- [ElectionsController] هي فئة تنفيذ خدمة الويب؛
14.4. تكوين Maven
المشروع هو مشروع Maven تم تكوينه بواسطة ملف [pom.xml] التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-webjson-metier-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-webjson-metier-dao-spring-data</name>
<description>couche métier exposée comme un service web / jSON</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- business layer -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
</dependency>
<!-- layer MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- الأسطر 19–23: التبعية لأرشيف طبقة [الأعمال]. هذا هو الأرشيف الذي أنشأناه في الفقرة 14؛
- الأسطر 25–28: التبعية لتطبيق Spring MVC؛
14.5. تكوين Spring
![]() |
تقوم فئة [WebConfig] بتكوين خدمة الويب:
package elections.webjson.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.metier.config.MetierConfig;
@EnableWebMvc
@Import({ MetierConfig.class })
@ComponentScan({ "elections.webjson.service" })
public class WebConfig {
// -------------------------------- layer configuration [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// mapper jSON
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- تم شرح معنى هذا التكوين في القسم 13.5.3.1. سنشرح فقط الميزات الجديدة:
- السطر 22: نقوم باستيراد ملف التكوين من طبقة [business] للاستفادة من جميع حبوبها؛
- السطر 23: نحدد أن الفاصوليا الأخرى ستوجد في المجلد [elections.webjson.server.service]؛
14.6. فئة تشغيل خدمة الويب
![]() |
تقوم فئة [Boot] بتشغيل خدمة الويب على النحو التالي:
package elections.webjson.boot;
import org.springframework.boot.SpringApplication;
import elections.webjson.config.WebConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(WebConfig.class, args);
}
}
- السطر 10: ستستخدم الطريقة الثابتة [SpringApplication.run] ملف التكوين [WebConfig]. وبفضل التعليق التوضيحي [@EnableAutoConfiguration]، سيقوم Spring Boot بتشغيل خادم Tomcat ونشر خدمة الويب عليه؛
14.7. الاستجابة من عناوين URL لخدمة الويب
![]() |
ترسل جميع عناوين URL لخدمة الويب / JSON نفس نوع الاستجابة:
package elections.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any error messages
private List<String> messages;
// the body of the reply
private T body;
// manufacturers
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters and setters
...
}
تم عرض هذه الفئة ومناقشتها في القسم 13.5.5.3.
14.8. تنفيذ خدمة الويب / JSON
![]() |
يتم تنفيذ خدمة الويب /JSON بواسطة فئة [ElectionsController] التالية:
package elections.webjson.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.metier.service.IElectionsMetier;
@Controller
public class ElectionsController {
// spring dependencies
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private IElectionsMetier metier;
@RequestMapping(value = "/getElectionsConfig", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getElectionsConfig() throws JsonProcessingException {
// answer
Response<ElectionsConfig> response;
try {
response = new Response<>(0, null,
new ElectionsConfig(metier.getNbSiegesAPourvoir(), metier.getSeuilElectoral()));
} catch (ElectionsException e1) {
response = new Response<>(e1.getCode(), e1.getErreurs(), null);
} catch (RuntimeException e2) {
response = new Response<>(1000, getErreursForException(e2), null);
}
// answer
return jsonMapper.writeValueAsString(response);
}
@RequestMapping(value = "/getListesElectorales", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getListesElectorales() throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/setListesElectorales", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String setListesElectorales(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/calculerSieges", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String calculerSieges(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
// private methods -----------------------------
// list of RuntimeException error messages
private List<String> getErreursForException(Exception e) {
// retrieve the list of exception error messages
Throwable cause = e;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
}
المهمة: اتبع الخطوات الواردة في القسم 13.5.5، وأكمل الكود الخاص بفئة [ElectionsController].
ملاحظات:
- لا توجد مرشحات JSON هنا لأن الجدولين [CONF] و[LISTES] غير مرتبطين بعلاقة مفتاح خارجي، مما يبسط كود خدمة الويب بشكل كبير؛
- لا تنسَ الإشارات التوضيحية المختلفة الضرورية في Spring؛
- سيتم تسمية عناوين URL تبعًا للأساليب المرتبطة بها؛
- يتم استدعاء الطريقة [setListeElectorales] من خلال عملية [POST]. القيمة المرسلة هي مصفوفة القوائم المتنافسة (من النوع ListeElectorale[]) مع سماتها [المقاعد، الأصوات، تم استبعادها]، والتي يجب حفظها في قاعدة البيانات. تُرجع هذه الطريقة كائن [Response<Void>] يحتوي على حقل [status=0] في حالة عدم وجود أخطاء، وإلا تُرجع قيمة أخرى؛
- يتم استدعاء الطريقة [calculateSeats] باستخدام عملية [POST]. القيمة المرسلة هي مصفوفة القوائم المتنافسة (من النوع ElectoralList[]) مع سماتها [name, votes]. تُرجع هذه الطريقة [Response<ElectoralList[]>] مع، كنصها الأساسي، القوائم الانتخابية مع حقول [seats, eliminated] المُهيأة؛
14.9. الاختبارات
بعد تشغيل خدمة الويب، قم بإجراء الاختبارات التالية للتأكد من أن خدمة الويب تعمل بشكل صحيح باستخدام الأداة المساعدة [Advanced Rest Client]:
![]() |
رد JSON على الطلب السابق هو كما يلي [1]:
![]() |
1 ![]() | 2 ![]() |
في [2]، انسخ الرد إلى الحافظة، ثم الصقه في أي محرر نصوص [3]:
![]() |
افصل قيمة حقل [body] وقم بتغيير، على سبيل المثال، الأصوات المخصصة للقوائم. أدناه [4]، قمنا بتعيين الأصوات لجميع القوائم على 100:
![]() |
تأكد من أن سلسلة JSON الخاصة بك تبدأ بـ [ وتنتهي بـ ]. تُستخدم هذه الأحرف لتحديد مصفوفة JSON. في [5]، الصق سلسلة JSON أعلاه. ستكون هذه هي القيمة التي سيتم إرسالها إلى عنوان URL التالي. للقيام بذلك، حدد طريقة HTTP [POST] [7].
![]() |
- في [6]، اطلب عنوان URL [setListesElectorales]. يتم طلب عنوان URL هذا باستخدام طلب POST. القيمة المرسلة هي مصفوفة JSON للقوائم المتنافسة، والتي يجب حفظ نتائجها في قاعدة البيانات؛
يتم الحصول على النتيجة التالية:
![]() |
يشير الحقل [status=0] إلى عدم وجود أخطاء. للتحقق من ذلك، اطلب القوائم المتنافسة مرة أخرى وتأكد من تطبيق التغييرات التي أجريتها على القوائم:
![]() |
نقوم بإرسال طلب [POST] آخر لحساب المقاعد التي فازت بها القوائم:
![]() |
- في [1]: عنوان URL لحساب المقاعد؛
- في [2]: نرسل طلب [POST]؛
- في [3]: القوائم المتنافسة. نضبط حقل [votes] على القيم الواردة في البرنامج التعليمي، ونضبط جميع [seats] على 0، ونضبط جميع حقول [eliminate] على false؛
والنتيجة التي تم الحصول عليها هي كما يلي:
![]() |

























