15. [TD]: Creating a client for the web service
Keywords: multi-layer architecture, Spring, dependency injection, web service / JSON, client / server
15.1. Support
![]() |
The projects for this chapter can be found in the [support / chap-15] folder.
15.2. The client/server architecture
We want to create the following client/server architecture:
![]() |
The [ui] layer will be the one already developed in sections 9 and 10. This will be possible because the [business] layer above will implement the same interface [IElectionsMetier] as the [business] layer in section 8:
package elections.client.metier;
import elections.client.entities.ListeElectorale;
public interface IElectionsMetier {
// get the lists in competition
public ListeElectorale[] getListesElectorales();
// the number of seats to be filled
public int getNbSiegesAPourvoir();
// the electoral threshold
public double getSeuilElectoral();
// recording results
public void recordResultats(ListeElectorale[] listesElectorales);
// calculating seats
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
}
In Section 7, the [DAO] layer exchanged data with a DBMS. Here, the [DAO] layer exchanges data with a web server / JSON.
Initially, we will focus on the following architecture:
![]() |
15.3. The Eclipse Project
The Eclipse project is as follows:
![]() | ![]() | ![]() |
This structure mirrors that of the example project in Section 13.6.1. We will follow the same approach.
15.4. Maven Configuration
This is the one described in Section 13.6.2.
15.5. Implementation of the [DAO] layer
![]() |
![]() |
- The [elections.client.config] package contains the Spring configuration for the [DAO] layer;
- The [elections.client.dao] package contains the implementation of the [DAO] layer;
- the [elections.client.entities] package contains the objects exchanged with the web service / JSON;
- The [elections.client.business] package contains the [business] layer
- The [elections.client.ui] package contains the [UI] layer
15.5.1. Configuration of the [business] layer
![]() |
The [MetierConfig] class handles the Spring configuration of the [business] layer. Its code is as follows:
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 {
// constants
static private final int TIMEOUT = 1000;
static private final String URL_WEBJSON = "http://localhost:8080";
@Bean
public RestTemplate restTemplate(int timeout) {
// creation of the RestTemplate component
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(factory);
// exchange timeout
factory.setConnectTimeout(timeout);
factory.setReadTimeout(timeout);
// result
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();
}
}
This code was explained in Section 13.6.3.1. It is simpler because there are no JSON filters to manage here.
15.5.2. The entities
![]() |
The entities handled by the [DAO] and [business] layers are the ones they exchange with the web service / JSON. These are objects of type [ElectionsConfig] and [VoterList]. On the server side, these entities had JPA persistence annotations. Here, those annotations have been removed. We’re including the entity code again for reference:
[AbstractEntity]
package spring.webjson.client.entities;
public abstract class AbstractEntity {
// properties
protected Long id;
protected Long version;
// manufacturers
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// redefine [equals] and [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 and setters
...
}
[ElectionsConfig]
package elections.webjson.client.entities;
public class ElectionsConfig extends AbstractEntity {
// fields
private int nbSiegesAPourvoir;
private double seuilElectoral;
// manufacturers
public ElectionsConfig() {
}
public ElectionsConfig(int nbSiegesAPourvoir, double seuilElectoral) {
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters and setters
...
}
[VoterList]
package elections.webjson.client.entities;
public class ListeElectorale extends AbstractEntity {
// fields
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// manufacturers
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
setNom(nom);
setVoix(voix);
setSieges(sieges);
setElimine(elimine);
}
// getters and setters
...
}
15.5.3. The [DAO] layer interface
![]() |
The [DAO] layer has the following [IClientDao] interface:
package elections.client.dao;
public interface IClientDao {
// generic request
String getResponse(String url, String jsonPost);
}
The interface has only one method [getResponse]:
- the first parameter is the URL of the server to query;
- the second parameter is the JSON value to be posted, null if there is nothing to post;
- the result is the JSON string of an [Response<T>] object, where the [Response] class was described in Section 14.7;
15.5.4. Implementation of communication with the web service / JSON
![]() |
The [ClientDao] class implements the [IClientDao] interface as follows:
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 {
// data
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// generic request
@Override
public String getResponse(String url, String jsonPost) {
try {
// url : URL to contact
// jsonPost: the jSON value to be posted
// request execution
RequestEntity<?> request;
if (jsonPost != null) {
// query POST
request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
.header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
} else {
// query GET
request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
.accept(MediaType.APPLICATION_JSON).build();
}
// execute the query
return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
}).getBody();
} catch (URISyntaxException e1) {
throw new ElectionsException(200, e1);
} catch (RuntimeException e2) {
throw new ElectionsException(201, e2);
}
}
}
This code is described in section 13.6.3.6.
15.6. Implementation of the [business] layer
![]() |
As mentioned, the [business] layer has the same interface [IElectionsMetier] as in section 8.4:
package elections.client.metier;
import elections.client.entities.ListeElectorale;
public interface IElectionsMetier {
// get the lists in competition
public ListeElectorale[] getListesElectorales();
// the number of seats to be filled
public int getNbSiegesAPourvoir();
// the electoral threshold
public double getSeuilElectoral();
// recording results
public void recordResultats(ListeElectorale[] listesElectorales);
// calculating seats
public ListeElectorale[] calculerSieges(ListeElectorale[] listesElectorales);
}
This interface is implemented by the following [ElectionsMetier] class:
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;
// election configuration
private ElectionsConfig electionsConfig;
@PostConstruct
public void init() {
// mappers jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// request
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse("/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// mistake?
if (response.getStatus() != 0) {
// 1 exception is thrown
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) {
...
}
// list of exception error messages
private List<String> getMessagesForException(Exception exception) {
// retrieve the list of exception error messages
Throwable cause = exception;
List<String> erreurs = new ArrayList<String>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
}
The [Response] type used on line 37 is the web server/JSON response described in section 14.7;
Task: Following Section 13.6.3.7, complete the [ElectionsMetier] class;
15.7. The JUnit test
Let’s return to the client/server architecture currently under construction:
![]() |
The [JUnit] layer [1] communicates with the server’s [Business] layer [5] through layers [2–4]. By ensuring that the [Business] layers [2] and [5] have the same interface, we make layers [2–4] transparent. Layer [1] appears to communicate directly with layer [5]. The interesting point is that in [1] we will be able to use the JUnit test that was used to test the [Business] layer [5].
![]() |
Task: Run the project’s JUnit test to verify your implementation of both the server and its client.
15.8. Implementation of the [UI] layer
Let’s return to the architecture we want to build:
![]() |
Now that the [Business] layer [2] has been built and tested, we can build the [UI] layer [1].
![]() |
Since the [IElectionsMetier] interface of the [Business] layer is identical to that of the project described in paragraph 8, we can [3] copy the [UI] layer project from paragraph 10. This project was a NetBeans project. Simply copy and paste the relevant Java classes from NetBeans to Eclipse. Once this is done, there are some package and import adjustments to make.
We will do the same for the executable classes in the [elections.client.boot] package [4].
The [AbstractBootElections] class is as follows:
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 context retrieval
protected AnnotationConfigApplicationContext ctx;
public void run() {
// instantiation layer [ui]
IElectionsUI electionsUI = null;
try {
// spring context retrieval
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// ui] layer recovery
electionsUI = getUI();
...
- Line 19: The Spring context defined by the [UiConfig] configuration class is instantiated. This class is as follows:
![]() |
The [UiConfig] class is as follows:
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 {
}
- Line 6: Imports the beans from the [business] layer;
- Line 7: We indicate that there are Spring beans in the [elections.client.ui] package;
Task: Verify that the console and Swing versions of the [ui] layer are working.















