15. [TD]: aanmaken van een client voor de webservice
Trefwoorden: meerlaagse architectuur, Spring, afhankelijkheidsinjectie, webservice / jSON, client / server
15.1. Support
![]() |
De projecten uit dit hoofdstuk zijn te vinden in de map [support / chap-15].
15.2. De client/server-architectuur
We willen de volgende client/server-architectuur opzetten:
![]() |
De laag [ui] is dezelfde die al in de paragrafen 9 en 10 is ontwikkeld. Dit is mogelijk omdat de hierboven genoemde laag [métier] dezelfde interface [IElectionsMetier] implementeert als de laag [métier] uit paragraaf 8:
package elections.client.metier;
import elections.client.entities.ListeElectorale;
public interface IElectionsMetier {
// de concurrerende lijsten ophalen
public ListeElectorale[] getListesElectorales();
// het aantal te vervullen zetels
public int getNbSiegesAPourvoir();
// de kiesdrempel
public double getSeuilElectoral();
// de registratie van de resultaten
public void recordResultats(ListeElectorale[] listesElectorales);
// de berekening van de zetels
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
}
In paragraaf 7 wisselde de laag [DAO] gegevens uit met een SGBD. Hier wisselt de laag [DAO] gegevens uit met een webserver / jSON.
In eerste instantie zullen we ons richten op de volgende architectuur:
![]() |
15.3. Het Eclipse-project
Het Eclipse-project ziet er als volgt uit:
![]() | ![]() | ![]() |
Deze structuur komt overeen met die van het voorbeeldproject in paragraaf 13.6.1. We zullen dezelfde aanpak volgen.
15.4. Maven-configuratie
Deze is beschreven in paragraaf 13.6.2.
15.5. Implementatie van de laag [DAO]
![]() |
![]() |
- het pakket [elections.client.config] bevat de Spring-configuratie van de laag [DAO];
- het pakket [elections .client.dao] bevat de implementatie van de laag [DAO];
- het pakket [elections .client.entities] bevat de objecten die worden uitgewisseld met de webservice / jSON;
- het pakket [elections .client.metier] bevat de laag [métier]
- het pakket [elections .client.ui] bevat de laag [UI]
15.5.1. Configuratie van de laag [métier]
![]() |
De klasse [MetierConfig] zorgt voor de Spring-configuratie van de laag [métier]. De code ervan is als volgt:
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 {
// constanten
static private final int TIMEOUT = 1000;
static private final String URL_WEBJSON = "http://localhost:8080";
@Bean
public RestTemplate restTemplate(int timeout) {
// aanmaken van de component RestTemplate
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(factory);
// time-out van de uitwisselingen
factory.setConnectTimeout(timeout);
factory.setReadTimeout(timeout);
// resultaat
return restTemplate;
}
@Bean
public int timeout() {
return TIMEOUT;
}
@Bean
public String urlWebJson() {
return URL_WEBJSON;
}
// mapper jSON
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
Deze code is uitgelegd in paragraaf 13.6.3.1. Deze is eenvoudiger omdat er hier geen jSON-filters hoeven te worden beheerd.
15.5.2. De entiteiten
![]() |
De entiteiten die door de lagen [DAO] en [métier] worden verwerkt, zijn de entiteiten die zij uitwisselen met de webservice / jSON. Dit zijn de objecten van het type [ElectionsConfig] en [ListeElectorale]. Aan de serverzijde hadden deze entiteiten persistentie-annotaties JPA. Hier zijn deze annotaties verwijderd. Ter herinnering geven we de code van de entiteiten nogmaals weer:
[AbstractEntity]
package spring.webjson.client.entities;
public abstract class AbstractEntity {
// eigenschappen
protected Long id;
protected Long version;
// constructors
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// herdefinitie van [equals] en [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();
}
// getters en setters
...
}
[ElectionsConfig]
package elections.webjson.client.entities;
public class ElectionsConfig extends AbstractEntity {
// velden
private int nbSiegesAPourvoir;
private double seuilElectoral;
// constructors
public ElectionsConfig() {
}
public ElectionsConfig(int nbSiegesAPourvoir, double seuilElectoral) {
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters en setters
...
}
[ListeElectorale]
package elections.webjson.client.entities;
public class ListeElectorale extends AbstractEntity {
// velden
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// constructors
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
setNom(nom);
setVoix(voix);
setSieges(sieges);
setElimine(elimine);
}
// getters en setters
...
}
15.5.3. De interface van de laag [DAO]
![]() |
De laag [DAO] heeft de volgende interface [IClientDao]:
package elections.client.dao;
public interface IClientDao {
// generieke query
String getResponse(String url, String jsonPost);
}
De interface heeft slechts één methode, [getResponse]:
- de eerste parameter is de URL van de te raadplegen server;
- de tweede parameter is de waarde jSON van de te verzenden waarde, null als er niets te verzenden is;
- het resultaat is de tekenreeks jSON van een object [Response<T>], waarbij de klasse [Response] in paragraaf 14.7 is beschreven;
15.5.4. Implementatie van de uitwisselingen met de webservice / jSON
![]() |
De klasse [ClientDao] implementeert de interface [IClientDao] als volgt:
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 {
// gegevens
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// generieke aanvraag
@Override
public String getResponse(String url, String jsonPost) {
try {
// URL: URL (contact opnemen)
// jsonPost: de waarde jSON moet worden verzonden
// verzoek uitvoeren
RequestEntity<?> request;
if (jsonPost != null) {
// verzoek POST
request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
.header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
} else {
// verzoek GET
request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
.accept(MediaType.APPLICATION_JSON).build();
}
// de query wordt uitgevoerd
return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
}).getBody();
} catch (URISyntaxException e1) {
throw new ElectionsException(200, e1);
} catch (RuntimeException e2) {
throw new ElectionsException(201, e2);
}
}
}
Deze code is beschreven in paragraaf 13.6.3.6.
15.6. Implementatie van de laag [métier]
![]() |
Zoals gezegd heeft de laag [métier] dezelfde interface [IElectionsMetier] als in paragraaf 8.4:
package elections.client.metier;
import elections.client.entities.ListeElectorale;
public interface IElectionsMetier {
// de lijsten van de deelnemende kandidaten ophalen
public ListeElectorale[] getListesElectorales();
// het aantal te vervullen zetels
public int getNbSiegesAPourvoir();
// de kiesdrempel
public double getSeuilElectoral();
// de registratie van de resultaten
public void recordResultats(ListeElectorale[] listesElectorales);
// de zetelverdeling
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
}
Deze interface wordt geïmplementeerd door de volgende klasse [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;
// de opzet van de verkiezing
private ElectionsConfig electionsConfig;
@PostConstruct
public void init() {
// toewijzers jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// verzoek
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse("/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// fout?
if (response.getStatus() != 0) {
// er wordt 1 uitzondering gegenereerd
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) {
...
}
// lijst met foutmeldingen van een uitzondering
private List<String> getMessagesForException(Exception exception) {
// de lijst met foutmeldingen van de uitzondering wordt opgehaald
Throwable cause = exception;
List<String> erreurs = new ArrayList<String>();
while (cause != null) {
// het bericht wordt alleen opgehaald als het !=null is en niet leeg
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// volgende oorzaak
cause = cause.getCause();
}
return erreurs;
}
}
Het type [Response] dat op regel 37 wordt gebruikt, is het antwoord van de webserver / jSON zoals beschreven in paragraaf 14.7;
Te doen: vul, volgens paragraaf 13.6.3.7, de klasse [ElectionsMetier] aan;
15.7. De Junit-test
Laten we terugkeren naar de client/server-architectuur die we aan het opbouwen zijn:
![]() |
De laag [JUnit] [1] communiceert met de laag [Métier] van de server [5] via de lagen [2-4]. Door ervoor te zorgen dat de lagen [Métier], [2] en [5] dezelfde interface hebben, worden de lagen [2-4] transparant gemaakt. De laag [1] lijkt rechtstreeks te communiceren met de laag [5]. Het interessante is dat we in [1] de test JUnit kunnen gebruiken die eerder was gebruikt om de lagen [Métier] en [5] te testen.
![]() |
Te doen: voer de test JUnit van het project uit om zowel de implementatie als de server en de client te controleren.
15.8. Implementatie van de laag [UI]
Laten we terugkeren naar de architectuur die we willen bouwen:
![]() |
Nu de laag [métier] [2] is gebouwd en getest, kunnen we de laag [ui] [1] bouwen.
![]() |
Aangezien de interface [IElectionsMetier] van de laag [métier] identiek is aan die van het in paragraaf 8 beschreven project, kunnen we in [3] het project van de laag [ui] uit paragraaf 10 kopiëren. Dit project was een NetBeans-project. Het volstaat om de betreffende Java-klassen vanuit NetBeans naar Eclipse te kopiëren en te plakken. Nadat dit is gebeurd, moeten er aanpassingen worden gedaan aan de pakketten en de imports.
Hetzelfde doen we voor de uitvoerbare klassen van de pakketten [elections.client.boot] en [4].
De klasse [AbstractBootElections] is als volgt:
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 {
// ophalen van de Spring-context
protected AnnotationConfigApplicationContext ctx;
public void run() {
// instantiëren van de laag [ui]
IElectionsUI electionsUI = null;
try {
// ophalen van de Spring-context
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// ophalen van de laag [ui]
electionsUI = getUI();
...
- regel 19: de Spring-context die wordt gedefinieerd door de configuratieklasse [UiConfig] wordt geïnstantieerd. Deze klasse is als volgt:
![]() |
De klasse [UiConfig] is als volgt:
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 {
}
- regel 6: de beans van de laag [métier] worden geïmporteerd;
- regel 7: er wordt aangegeven dat er Spring-beans in het pakket [elections.client.ui] zitten;
Te doen: controleer of de console- en Swing-versies van de laag [ui] werken.















