Skip to content

15. [TD]: ایجاد یک کلاینت برای سرویس وب

کلیدواژه‌ها: معماری چندلایه، اسپرینگ، تزریق وابستگی، سرویس وب / jSON، کلاینت / سرور

15.1. Support

  

پروژه‌های این فصل را می‌توان در پوشه [support / chap-15] یافت.

15.2. معماری کلاینت/سرور

ما می‌خواهیم معماری کلاینت/سرور زیر را ایجاد کنیم:

لایه [ui] همان لایه‌ای خواهد بود که در بخش‌های ۹ و ۱۰ قبلاً توسعه یافته است. این امر ممکن خواهد بود زیرا لایه [métier] که در بالا ذکر شد، همان رابط [IElectionsMetier] را پیاده‌سازی خواهد کرد که لایه [métier] در پاراگراف ۸ پیاده‌سازی کرده است:


package elections.client.metier;

import elections.client.entities.ListeElectorale;

public interface IElectionsMetier {

    // فهرست‌های رقیب را بازیابی می‌کند
    public ListeElectorale[] getListesElectorales();

    // تعداد کرسی‌هایی که باید پر شوند
    public int getNbSiegesAPourvoir();

    //آستانهٔ انتخاباتی
    public double getSeuilElectoral();

    //ثبت نتایج
    public void recordResultats(ListeElectorale[] listesElectorales);

    // تخصیص کرسی‌ها
    public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);

}

در پاراگراف ۷، لایه [DAO] داده‌ها را با SGBD مبادله کرد. در اینجا، لایه [DAO] داده‌ها را با یک سرور وب / jSON مبادله می‌کند.

برای شروع، ما بر روی معماری زیر تمرکز خواهیم کرد:

15.3. پروژه اکلیپس

پروژه Eclipse به شرح زیر است:

این ساختار مشابه ساختار پروژهٔ نمونه در بخش 13.6.1 است. ما از همان رویکرد پیروی خواهیم کرد.

15.4. پیکربندی Maven

این همان‌طور که در بخش 13.6.2 توضیح داده شده است.

15.5. پیاده‌سازی لایه [DAO]

  
  • پکیج [elections.client.config] حاوی پیکربندی Spring برای لایه [DAO] است؛
  • پکیج [elections .client.dao] شامل پیاده‌سازی لایه [DAO] است؛
  • پکیج [elections .client.entities] حاوی اشیایی است که با سرویس وب / jSON مبادله می‌شوند؛
  • پکیج [elections .client.metier] شامل لایه [métier] است
  • پکیج [elections .client.ui] شامل لایه [UI] است

15.5.1. پیکربندی لایه [métier]

  

کلاس [MetierConfig] لایه [métier] را با استفاده از Spring پیکربندی می‌کند. کد آن به شرح زیر است:


package elections.client.config;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Scope;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

@ComponentScan({ "elections.client.dao","elections.client.metier" })
public class MetierConfig {

    // ثوابت
    static private final int TIMEOUT = 1000;
    static private final String URL_WEBJSON = "http://localhost:8080";

    @Bean
    public RestTemplate restTemplate(int timeout) {
        // ایجاد مؤلفه RestTemplate
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(factory);
        // انقضای تبادل
        factory.setConnectTimeout(timeout);
        factory.setReadTimeout(timeout);
        // نتیجه
        return restTemplate;
    }

    @Bean
    public int timeout() {
        return TIMEOUT;
    }

    @Bean
    public String urlWebJson() {
        return URL_WEBJSON;
    }

    // نقشه‌کش jSON
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }
}

این کد در بخش 13.6.3.1 توضیح داده شد. ساده‌تر است زیرا در اینجا هیچ فیلتری از نوع jSON برای مدیریت وجود ندارد.

15.5.2. این اشیاء

  

اشیایی که توسط لایه‌های [DAO] و [métier] مدیریت می‌شوند، همان‌هایی هستند که با سرویس وب /jSON مبادله می‌کنند. این‌ها اشیایی از نوع [ElectionsConfig] و [ListeElectorale] هستند. در سمت سرور، این انتیته‌ها دارای انوتیشن‌های پایداری JPA بودند. در اینجا، این انوتیشن‌ها حذف شده‌اند. برای یادآوری، کد انتیته‌ها را مجدداً ارائه می‌کنیم:

[AbstractEntity]


package spring.webjson.client.entities;


public abstract class AbstractEntity {
    // ویژگی‌ها
    protected Long id;
    protected Long version;

    // سازنده‌ها
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // override [equals] و [hashcode]
    @Override
    public int hashCode() {
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return id != null && this.id == other.id.longValue();
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
    ...
}

[ElectionsConfig]


package elections.webjson.client.entities;


public class ElectionsConfig extends AbstractEntity {

    // fields
    private int nbSiegesAPourvoir;
    private double seuilElectoral;

    // سازنده‌ها
    public ElectionsConfig() {

    }

    public ElectionsConfig(int nbSiegesAPourvoir, double seuilElectoral) {
        this.nbSiegesAPourvoir = nbSiegesAPourvoir;
        this.seuilElectoral = seuilElectoral;
    }

    // گیرنده و تنظیم‌کننده
    ...
}

[ListeElectorale]


package elections.webjson.client.entities;


public class ListeElectorale extends AbstractEntity {

    // fields
    private String nom;
    private int voix;
    private int sieges;
    private boolean elimine;

    // سازنده‌ها
    public ListeElectorale() {
    }

    public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
        setNom(nom);
        setVoix(voix);
        setSieges(sieges);
        setElimine(elimine);
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
    ...
}

15.5.3. رابط لایه [DAO]

  

لایه [DAO] دارای رابط [IClientDao] زیر است:


package elections.client.dao;

public interface IClientDao {

    // پرسش عمومی
    String getResponse(String url, String jsonPost);

}

این رابط تنها یک متد دارد، [getResponse]:

  • پارامتر اول، URL مربوط به سرور مورد پرس‌وجو است؛
  • پارامتر دوم مقدار jSON برای مقداری است که باید ارسال شود، یا null اگر چیزی برای ارسال وجود نداشته باشد؛
  • نتیجه، رشته jSON یک شیء [Response<T>] است، که در آن کلاس [Response] در بخش 14.7 توصیف شده است؛

15.5.4. پیاده‌سازی مبادلات با سرویس وب / jSON

  

کلاس [ClientDao] رابط [IClientDao] را به شرح زیر پیاده‌سازی می‌کند:


package elections.client.dao;

import java.net.URI;
import java.net.URISyntaxException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import elections.client.entities.ElectionsException;

@Component
public class ClientDao implements IClientDao {

    // داده‌ها
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;

    // پرسش عمومی
    @Override
    public String getResponse(String url, String jsonPost) {

        try {
            // URL: URL – تماس
            //jsonPost: مقدار jSON برای ارسال

            // اجرای درخواست
            RequestEntity<?> request;
            if (jsonPost != null) {
                // درخواست POST
                request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
            } else {
                // درخواست GET
                request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .accept(MediaType.APPLICATION_JSON).build();
            }
            //پرس‌وجو اجرا می‌شود
            return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
            }).getBody();
        } catch (URISyntaxException e1) {
            throw new ElectionsException(200, e1);
        } catch (RuntimeException e2) {
            throw new ElectionsException(201, e2);
        }
    }
}

این کد در بخش 13.6.3.6 توصیف شده است.

15.6. پیاده‌سازی لایه [métier]

 

همان‌طور که ذکر شد، لایه [métier] دارای همان رابط [IElectionsMetier] است که در بخش 8.4 آمده است:


package elections.client.metier;

import elections.client.entities.ListeElectorale;

public interface IElectionsMetier {

    //برای بازیابی فهرست نامزدها
    public ListeElectorale[] getListesElectorales();

    // تعداد کرسی‌های قابل پر شدن
    public int getNbSiegesAPourvoir();

    //آستانهٔ انتخاباتی
    public double getSeuilElectoral();

    //ثبت نتایج
    public void recordResultats(ListeElectorale[] listesElectorales);

    // محاسبه کرسی‌ها
    public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);

}

این رابط توسط کلاس زیر پیاده‌سازی شده است، [ElectionsMetier]:


package elections.client.metier;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.client.dao.IClientDao;
import elections.client.entities.ElectionsConfig;
import elections.client.entities.ElectionsException;
import elections.client.entities.ListeElectorale;

@Component
public class ElectionsMetier implements IElectionsMetier {

    @Autowired
    private IClientDao dao;
    @Autowired
    private ApplicationContext context;

    // پیکربندی انتخابات
    private ElectionsConfig electionsConfig;

    @PostConstruct
    public void init() {
        // نقشه‌برداران jSON
        ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
        try {
            // پرس‌وجو
            Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse("/getElectionsConfig", null),
                    new TypeReference<Response<ElectionsConfig>>() {
                    });
            // خطا؟
            if (response.getStatus() != 0) {
                // یک استثنا پرتاب می‌شود
                throw new ElectionsException(response.getStatus(), response.getMessages());
            } else {
                electionsConfig = response.getBody();
            }
        } catch (ElectionsException e1) {
            throw e1;
        } catch (Exception e2) {
            throw new ElectionsException(100, getMessagesForException(e2));
        }
    }

    @Override
    public ListeElectorale[] getListesElectorales() {
        ...
    }

    @Override
    public int getNbSiegesAPourvoir() {
        return electionsConfig.getNbSiegesAPourvoir();
    }

    @Override
    public double getSeuilElectoral() {
        return electionsConfig.getSeuilElectoral();
    }

    @Override
    public void recordResultats(ListeElectorale[] listesElectorales) {
    ...
    }

    @Override
    public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales) {
...
    }

    // فهرست پیام‌های خطا برای یک استثنا
    private List<String> getMessagesForException(Exception exception) {
        // بازیابی لیست پیام‌های خطا برای استثنا
        Throwable cause = exception;
        List<String> erreurs = new ArrayList<String>();
        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;
    }
}

نوع [Response] که در خط ۳۷ استفاده شده، پاسخ سرور وب / jSON است که در بخش ۱۴.۷ توضیح داده شده است؛


وظیفه: مطابق بخش 13.6.3.7، کلاس [ElectionsMetier] را تکمیل کنید؛


15.7. آزمون JUnit

بیایید به معماری کلاینت/سرور که در حال حاضر در حال توسعه است بازگردیم:

لایه‌های [JUnit] و [1] از طریق لایه‌های [2-4] با لایه [Métier] از سرور [5] ارتباط برقرار می‌کنند. با تضمین اینکه لایه‌های [Métier]، [2] و [5] دارای رابط یکسان هستند، لایه‌های [2-4] شفاف می‌شوند. لایه [1] به نظر می‌رسد مستقیماً با لایه [5] ارتباط برقرار می‌کند. نکته جالب این است که در [1] ما قادر خواهیم بود از تست JUnit استفاده کنیم، که برای آزمایش لایه [Métier] [5] به کار رفته بود.

  

وظیفه: تست JUnit پروژه را برای تأیید پیاده‌سازی شما از هر دو سرور و کلاینت آن اجرا کنید.


15.8. پیاده‌سازی لایه [UI]

بیایید به معماری‌ای که می‌خواهیم بسازیم بازگردیم:

اکنون که لایه‌های [métier] و [2] ساخته و آزمایش شده‌اند، می‌توانیم لایه‌های [ui] و [1] را بسازیم.

از آنجایی که رابط [IElectionsMetier] لایه [métier] با رابط پروژه توصیف‌شده در پاراگراف ۸ یکسان است، ما می‌توانیم، در [3]، پروژه را از لایه [ui] که در پاراگراف ۱۰ توصیف شده است، کپی کنیم. این پروژه یک پروژه NetBeans بود. به سادگی کلاس‌های جاوا مرتبط را از NetBeans در Eclipse کپی و پیست کنید. پس از انجام این کار، نیاز به انجام برخی تنظیمات در پکیج‌ها و importها خواهد بود.

ما همین کار را برای کلاس‌های اجرایی در بسته‌های [elections.client.boot] و [4] انجام خواهیم داد.

کلاس [AbstractBootElections] به شرح زیر است:


package elections.client.boot;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import elections.client.config.UiConfig;
import elections.client.entities.ElectionsException;
import elections.client.ui.IElectionsUI;

public abstract class AbstractBootElections {

    // زمینه Spring را بازیابی می‌کند
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        //لایه [ui] را ایجاد می‌کند
        IElectionsUI electionsUI = null;
        try {
            //بازیابی کانکسِتِ Spring
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // بازیابی لایه [ui]
            electionsUI = getUI();

...
  • خط ۱۹: کانکست Spring که توسط کلاس پیکربندی [UiConfig] تعریف شده است، نمونه سازی می‌شود. این کلاس به شرح زیر است:
  

کلاس [UiConfig] به شرح زیر است:


package elections.client.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.client.ui" })
public class UiConfig {
}
  • خط ۶: بین‌های لایه [métier] وارد می‌شوند؛
  • خط ۷: مشخص می‌کنیم که در پکیج [elections.client.ui]، بین‌های Spring وجود دارند؛

وظیفه: بررسی اینکه نسخه‌های کنسولی و Swing لایه [ui] در حال کار هستند.