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;
}
}
- 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.















