Skip to content

14. [TD]: عرض طبقة [metier] على الويب

الكلمات الرئيسية: بنية متعددة الطبقات، Spring، حقن التبعيات، خدمة الويب / jSON، العميل / الخادم.

لنعد إلى البنية الحالية لتطبيق TD:

سنقوم بتطوير هذه البنية إلى ما يلي:

من أجل عرض واجهة [IMetier] للطبقة الوظيفية على الويب. ولهذا الغرض، سنتبع المنهجية الموضحة في الفقرة 13.5.

14.1. Support

  

ستجد مشاريع هذا الفصل في المجلد [support / chap-14].

14.2. مشروع Eclipse الخاص بطبقة [métier]

  

14.2.1. تكوين Maven

مشروع الطبقة [métier] هو مشروع 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>

    <!-- التبعيات -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>
    <dependencies>
        <!-- الطبقة [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 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <!-- استخدم UTF-8 لكل شيء -->
        <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

  

مشروع الطبقة [métier] هو مشروع 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]. وبذلك تصبح جميع الفئات (beans) المحددة في هذا الملف متاحة؛
  • السطر 9: يجب البحث عن مكونات Spring أخرى في المجلد [elections.metier.service

14.2.3. تنفيذ الطبقة [métier]

  

تنفيذ الطبقة [métier] هو الذي تم تعريفه في الفقرة 8.5.

14.2.4. اختبار الطبقة [métier]

  

فئة الاختبار هي تلك الموصوفة في الفقرة 8.6.


المهمة المطلوبة: قم بتنفيذ مشروع الطبقة [métier] واجتياز اختبارها الفردي. قم بإنشاء أرشيف الطبقة في مستودع Maven المحلي (run as/ Maven / install).


14.3. مشروع Eclipse للطبقة [web]

طبقة الويب هي طبقة 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>
        <!-- طبقة الأعمال -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-spring-data</artifactId>
            <version>0.1.0</version>
        </dependency>
        <!-- طبقة 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: التبعية على أرشيف الطبقة [métier]. وهي التي أنشأناها في الفقرة 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 {
    // -------------------------------- تكوين طبقة [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);
    }
    // أداة التعيين jSON
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

}
  • تم توضيح معنى هذا التكوين في الفقرة 13.5.3.1. سنشرح فقط المستجدات:
  • السطر 22: يتم استيراد ملف تكوين الطبقة [métier] للاستفادة من جميع حبوبها؛
  • السطر 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> {

    // ----------------- الخصائص
    // حالة العملية
    private int status;
    // رسائل الخطأ المحتملة
    private List<String> messages;
    // نص الرد
    private T body;

    // المنشئات
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // مُستردات ومُعيّنات
...
}

تم عرض هذه الفئة ودراستها في الفقرة 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
    @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 {
        // الرد
        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);
        }
        // الرد
        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");
    }

    // الطرق الخاصة -----------------------------
    // قائمة رسائل الخطأ لـ RuntimeException
    private List<String> getErreursForException(Exception e) {
        // يتم استرداد قائمة رسائل الخطأ الخاصة بالاستثناء
        Throwable cause = e;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // يتم استرداد الرسالة فقط إذا كانت !=null وليست فارغة
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // السبب التالي
            cause = cause.getCause();
        }
        return erreurs;
    }

}

المهمة المطلوبة: باتباع ما تم في الفقرة 13.5.5، أكمل كود الفئة [ElectionsController].


ملاحظات:

  • لا توجد هنا مرشحات jSON لأن الجداول [CONF] و [LISTES] غير مرتبطة ببعضها البعض بعلاقة مفتاح أجنبي، مما يخفف بشكل كبير من كود خدمة الويب؛
  • لا تنسَ الإشارات التوضيحية المختلفة اللازمة لـ Spring؛
  • سنعطي URL أسماء الطرق المرتبطة بها؛
  • يتم استدعاء الطريقة [setListeElectorales] باستخدام عملية [POST]. القيمة المرسلة هي مصفوفة القوائم المتنافسة (من النوع ListeElectorale[]) مع سماتها [sieges, voix, elimine] التي يجب تسجيلها في قاعدة البيانات. تُرجع هذه الطريقة نوع [Response<Void>] مع حقل [status=0] في حالة عدم وجود أخطاء، وإلا تُرجع قيمة أخرى؛
  • يتم استدعاء الطريقة [calculerSieges] مع عملية [POST]. القيمة المرسلة هي مصفوفة القوائم المتنافسة (من النوع ListeElectorale[]) مع سماتها [nom, voix]. تُرجع هذه الطريقة نوع [Response<ListeElectorale[]>] مع القوائم الانتخابية كنص أساسي مع حقولها [sieges, elimine] المُهيأة؛

14.9. Tests

بعد تشغيل الخدمة الويب، ستجري الاختبارات التالية للتأكد من عمل الخدمة الويب بشكل صحيح باستخدام الأداة المساعدة [Advanced Rest Client]:

 

الرد jSON على الطلب السابق هو كما يلي [1]:

1
2

في [2]، انسخ الرد إلى الحافظة ثم انسخه إلى أي محرر نصوص [3]:

افصل قيمة الحقل [body] وقم بتغيير الأصوات في القوائم على سبيل المثال. أدناه [4]، نقوم بتغيير أصوات جميع القوائم إلى 100:

تأكد من أن السلسلة jSON تبدأ بـ [ et se termine par ]. تُستخدم هذه الأحرف لتحديد جدول jSON. في [5]، الصق السلسلة jSON أعلاه. ستكون هذه هي القيمة التي سيتم نشرها لـ URL التالية. للقيام بذلك، يجب تحديد الطريقة HTTP [POST] [7].

  • في [6]، اطلب URL [setListesElectorales]. يُطلب هذا URL مع POST. القيمة المنشورة هي الجدول jSON للقوائم المتنافسة التي يجب تسجيل نتائجها في قاعدة البيانات؛

ونحصل على النتيجة التالية:

 

يشير الحقل [status=0] إلى عدم وجود أخطاء. للتحقق من ذلك، أعد طلب قوائم المنافسة وتأكد من أن التعديلات التي أجريتها على القوائم قد تم أخذها في الاعتبار:

نقوم بإعادة إنشاء [POST] لحساب المقاعد التي حصلت عليها القوائم:

  • في [1]: URL لحساب المقاعد؛
  • إلى [2]: نقوم بإجراء [POST
  • في [3]: القوائم المتنافسة. نقوم بإعطاء الحقل [voix] قيم TD، وجميع قيم [sieges] تساوي 0، وجميع حقول [elimine] تساوي false؛

والنتيجة التي تم الحصول عليها هي التالية: