Skip to content

15. [TD]: web hizmeti için bir istemci oluşturma

Anahtar kelimeler: çok katmanlı mimari, Spring, bağımlılık enjeksiyonu, web hizmeti / jSON, istemci / sunucu

15.1. Support

  

Bu bölümdeki projeler [support / chap-15] klasöründe bulunur.

15.2. İstemci / sunucu mimarisi

Aşağıdaki istemci / sunucu mimarisini oluşturmak istiyoruz:

[ui] katmanı, 9. ve 10. paragraflarda daha önce geliştirilen katman olacaktır. Bu, yukarıdaki [métier] katmanının, 8. paragrafta yer alan [métier] katmanıyla aynı [IElectionsMetier] arayüzünü uygulayacağı için mümkün olacaktır:


package elections.client.metier;

import elections.client.entities.ListeElectorale;

public interface IElectionsMetier {

    // rekabet halindeki listeleri alır
    public ListeElectorale[] getListesElectorales();

    // doldurulacak koltuk sayısı
    public int getNbSiegesAPourvoir();

    // seçim barajı
    public double getSeuilElectoral();

    // sonuçların kaydı
    public void recordResultats(ListeElectorale[] listesElectorales);

    // koltuk hesaplaması
    public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);

}

7. paragrafta, [DAO] katmanı bir SGBD ile veri alışverişi yapıyordu. Burada ise [DAO] katmanı bir web sunucusu / jSON ile veri alışverişi yapmaktadır.

İlk olarak, şu mimariye odaklanacağız:

15.3. Eclipse Projesi

Eclipse projesi şu şekildedir:

Bu yapı, 13.6.1. paragrafındaki örnek projenin yapısını yansıtmaktadır. Aynı yaklaşımı izleyeceğiz.

15.4. Maven Yapılandırması

Bu, 13.6.2. paragrafında açıklanan yapıdır.

15.5. [DAO] katmanının uygulanması

  
  • [elections.client.config] paketi, [DAO] katmanının Spring yapılandırmasını içerir;
  • [elections .client.dao] paketi, [DAO] katmanının uygulamasını içerir;
  • [elections .client.entities] paketi, /jSON web hizmeti ile alışverişi yapılan nesneleri içerir;
  • [elections .client.metier] paketi, [métier] katmanını içerir
  • [elections .client.ui] paketi, [UI] katmanını içerir

15.5.1. [métier] katmanının yapılandırılması

  

[MetierConfig] sınıfı, [métier] katmanının Spring yapılandırmasını gerçekleştirir. Kod şöyledir:


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 {

    // sabitler
    static private final int TIMEOUT = 1000;
    static private final String URL_WEBJSON = "http://localhost:8080";

    @Bean
    public RestTemplate restTemplate(int timeout) {
        // RestTemplate bileşeninin oluşturulması
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(factory);
        // veri alışverişi zaman aşımı
        factory.setConnectTimeout(timeout);
        factory.setReadTimeout(timeout);
        // sonuç
        return restTemplate;
    }

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

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

    // eşleştirici jSON
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }
}

Bu kod, 13.6.3.1. paragrafında açıklanmıştır. Burada yönetilmesi gereken jSON filtreleri olmadığı için daha basittir.

15.5.2. Varlıklar

  

[DAO] ve [métier] katmanları tarafından işlenen varlıklar, /jSON web hizmeti ile karşılıklı olarak aktarılan varlıklardır. Bunlar, [ElectionsConfig] ve [ListeElectorale] türündeki nesnelerdir. Sunucu tarafında, bu varlıklar JPA kalıcılık ek açıklamalarına sahipti. Burada bu ek açıklamalar kaldırılmıştır. Hatırlatma amacıyla varlıkların kodunu tekrar veriyoruz:

[AbstractEntity]


package spring.webjson.client.entities;


public abstract class AbstractEntity {
    // özellikler
    protected Long id;
    protected Long version;

    // oluşturucular
    public AbstractEntity() {

    }

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

    // yeniden tanımlama [equals] ve [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();
    }

    // alıcı ve ayarlayıcılar
    ...
}

[ElectionsConfig]


package elections.webjson.client.entities;


public class ElectionsConfig extends AbstractEntity {

    // alanlar
    private int nbSiegesAPourvoir;
    private double seuilElectoral;

    // oluşturucular
    public ElectionsConfig() {

    }

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

    // getter ve setter'lar
    ...
}

[ListeElectorale]


package elections.webjson.client.entities;


public class ListeElectorale extends AbstractEntity {

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

    // yapıcılar
    public ListeElectorale() {
    }

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

    // getter ve setter'lar
    ...
}

15.5.3. [DAO] katmanının arayüzü

  

[DAO] katmanı, aşağıdaki [IClientDao] arayüzünü sunar:


package elections.client.dao;

public interface IClientDao {

    // genel sorgu
    String getResponse(String url, String jsonPost);

}

Arayüzde yalnızca tek bir yöntem bulunmaktadır: [getResponse]:

  • 1. parametre, sorgulanacak sunucunun URL değeridir;
  • ikinci parametre, gönderilecek değerin jSON değeridir; gönderilecek bir şey yoksa null olur;
  • sonuç, 14.7. paragrafta [Response] sınıfı tanımlanan bir [Response<T>] nesnesinin jSON dizesidir;

15.5.4. Web hizmeti ile veri alışverişinin uygulanması / jSON

  

[ClientDao] sınıfı, [IClientDao] arayüzünü aşağıdaki şekilde uygular:


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 {

    // veriler
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;

    // genel sorgu
    @Override
    public String getResponse(String url, String jsonPost) {

        try {
            // URL: URL ile iletişime geçin
            // jsonPost: jSON değeri gönderilmeli

            // isteğin yürütülmesi
            RequestEntity<?> request;
            if (jsonPost != null) {
                // POST isteği
                request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
            } else {
                // GET isteği
                request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .accept(MediaType.APPLICATION_JSON).build();
            }
            // sorgu çalıştırılıyor
            return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
            }).getBody();
        } catch (URISyntaxException e1) {
            throw new ElectionsException(200, e1);
        } catch (RuntimeException e2) {
            throw new ElectionsException(201, e2);
        }
    }
}

Bu kod, 13.6.3.6. paragrafında açıklanmıştır.

15.6. [métier] katmanının uygulaması

 

Daha önce de belirtildiği gibi, [métier] katmanı, 8.4. paragrafında olduğu gibi aynı [IElectionsMetier] arayüzüne sahiptir:


package elections.client.metier;

import elections.client.entities.ListeElectorale;

public interface IElectionsMetier {

    // yarışmadaki listeleri al
    public ListeElectorale[] getListesElectorales();

    // doldurulacak sandalye sayısı
    public int getNbSiegesAPourvoir();

    // seçim barajı
    public double getSeuilElectoral();

    // sonuçların kaydı
    public void recordResultats(ListeElectorale[] listesElectorales);

    // koltuk hesaplaması
    public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);

}

Bu arayüz, aşağıdaki [ElectionsMetier] sınıfı tarafından uygulanmaktadır:


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;

    // seçim yapılandırması
    private ElectionsConfig electionsConfig;

    @PostConstruct
    public void init() {
        // haritalayıcılar jSON
        ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
        try {
            // sorgu
            Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse("/getElectionsConfig", null),
                    new TypeReference<Response<ElectionsConfig>>() {
                    });
            // hata mı?
            if (response.getStatus() != 0) {
                // 1 istisna atılıyor
                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) {
...
    }

    // bir istisnaya ait hata mesajları listesi
    private List<String> getMessagesForException(Exception exception) {
        // istisnanın hata mesajları listesi alınır
        Throwable cause = exception;
        List<String> erreurs = new ArrayList<String>();
        while (cause != null) {
            // mesaj, yalnızca !=null ve boş değilse alınır
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // sonraki neden
            cause = cause.getCause();
        }
        return erreurs;
    }
}

  1. satırda kullanılan [Response] türü, 14.7. paragrafta açıklanan / jSON web sunucusunun yanıtıdır;

Yapılması gereken iş: 13.6.3.7 paragrafını takip ederek, [ElectionsMetier] sınıfını tamamlayın;


15.7. JUnit Testi

Şu anda oluşturulmakta olan istemci/sunucu mimarisine geri dönelim:

[JUnit] ve [1] katmanları, [2-4] katmanları aracılığıyla [5] sunucusunun [Métier] katmanıyla iletişim kurar. [Métier], [2] ve [5] katmanlarının aynı arayüze sahip olmasını sağlayarak, [2-4] katmanları şeffaf hale getirilir. [1] katmanı, [5] katmanıyla doğrudan iletişim kuruyormuş gibi görünür. İlginç olan nokta, [1]'te, [Métier] ve [5] katmanlarını test etmek için kullanılmış olan JUnit testini kullanabileceğimizdir.

  

Yapılması gereken iş: Uygulamanızı, sunucuyu ve istemciyi doğrulamak için projenin JUnit testini çalıştırın.


15.8. [UI] katmanının uygulaması

Oluşturmak istediğimiz mimariye geri dönelim:

Artık [métier] [2] katmanı oluşturulup test edildiğine göre, [ui] [1] katmanını oluşturabiliriz.

[métier] katmanının [IElectionsMetier] arayüzü, 8. paragrafta açıklanan projeninkiyle aynı olduğundan, [3]'te, 10. paragrafta yer alan [ui] katmanındaki projeyi kopyalayabiliriz. Bu proje bir NetBeans projesiydi. İlgili Java sınıflarını NetBeans'ten Eclipse'e kopyalayıp yapıştırmak yeterlidir. Bu işlem tamamlandıktan sonra, paketler ve içe aktarmalar konusunda bazı ayarlamalar yapılması gerekir.

Aynı işlemi, [elections.client.boot] ve [4] paketlerindeki çalıştırılabilir sınıflar için de yapacağız.

[AbstractBootElections] sınıfı şu şekildedir:


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 bağlamını alır
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // [ui] katmanının örneklenmesi
        IElectionsUI electionsUI = null;
        try {
            // Spring bağlamının alınması
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // [ui] katmanının alınması
            electionsUI = getUI();

...
  • 19. satır: [UiConfig] yapılandırma sınıfı tarafından tanımlanan Spring bağlamı örneklenir. Bu sınıf şöyledir:
  

[UiConfig] sınıfı şöyledir:


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 {
}
  • 6. satır: [métier] katmanındaki bean'ler içe aktarılır;
  • 7. satır: [elections.client.ui] paketinde Spring bean'leri olduğunu belirtir;

Yapılması gereken iş: [ui] katmanının konsol ve swing sürümlerinin çalıştığını kontrol edin.