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 [métier] layer above will implement the same [IElectionsMetier] interface as the [métier] layer from 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 paragraph 7, the [DAO] layer exchanged data with a SGBD. 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.metier] package contains the [métier] layer
- the [elections .client.ui] package contains the [UI] layer
15.5.1. Configuration of the [métier] layer
![]() |
The [MetierConfig] class configures the [métier] layer in Spring. 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. Entities
![]() |
The entities handled by the [DAO] and [métier] layers are those they exchange with the /jSON web service. These are objects of type [ElectionsConfig] and [ListeElectorale]. On the server side, these entities had JPA persistence annotations. Here, these annotations have been removed. We are providing 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
...
}
[ListeElectorale]
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 interface of the [DAO] layer
![]() |
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 of the data to be posted, null if there is nothing to post;
- the result is the string jSON of an object [Response<T>], where the class [Response] was described in section 14.7;
15.5.4. Implementation of exchanges with the web service / jSON
![]() |
The class [ClientDao] implements the interface [IClientDao] 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 was described in section 13.6.3.6.
15.6. Implementation of the [métier] layer
![]() |
As mentioned, the [métier] layer has the same [IElectionsMetier] interface 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 class [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;
// 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 type [Response] used on line 37 is the web server response / jSON 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] and [1] layers communicate with the [Métier] layer of the [5] server through the [2-4] layers. By ensuring that layers [Métier], [2], and [5] have the same interface, layers [2-4] are made 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 [Métier] and [5] layers.
![]() |
Task: Run the JUnit test for the project 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 [métier] [2] layer has been built and tested, we can build the [ui] [1] layer.
![]() |
Since the [IElectionsMetier] interface of the [métier] layer is identical to that of the project described in paragraph 8, we can copy the project from the [ui] layer in paragraph 10 into [3]. That project was a Netbeans project. Simply copy and paste the relevant Java classes from Netbeans into 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] and [4] packages.
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);
// layer recovery [ui]
electionsUI = getUI();
...
- Line 19: The Spring context defined by the configuration class [UiConfig] 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: We import the beans from the [métier] package;
- 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.















