14. [TD]: Web-expositie van de laag [metier]
Trefwoorden: meerlaagse architectuur, Spring, afhankelijkheidsinjectie, webservice / jSON, client / server.
Laten we terugkeren naar de huidige architectuur van de TD-applicatie:
![]() |
We gaan deze architectuur uitbreiden naar de volgende:
![]() |
om de interface [IMetier] van de bedrijfslaag op het web beschikbaar te maken. Hiervoor volgen we de methodologie die in paragraaf 13.5 wordt beschreven.
14.1. Support
![]() |
De projecten van dit hoofdstuk zijn te vinden in de map [support / chap-14].
14.2. Het Eclipse-project van de laag [métier]
![]() |
![]() |
14.2.1. Maven-configuratie
Het project van de laag [métier] is een Maven-project dat is geconfigureerd via het volgende bestand [pom.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
<!-- afhankelijkheden -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- laag [DAO] -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<!-- gebruik UTF-8 voor alles -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- regels 18-22: de afhankelijkheid van de laag [DAO] die in paragraaf 12 is opgebouwd;
- regels 23-34: de afhankelijkheden die nodig zijn voor de tests;
14.2.2. Spring-configuratie
![]() |
Het project van de laag [métier] is een Spring-project dat is geconfigureerd via het volgende bestand [MetierConfig]:
package elections.metier.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import elections.dao.config.DaoConfig;
@Import({ DaoConfig.class })
@ComponentScan({ "elections.metier.service" })
public class MetierConfig {
}
- we gebruiken hier niet de notatie [@Configuration], die van de klasse een Spring-configuratieklasse maakt. De aanwezigheid van de annotaties [@Import, @ComponentScan] maakt er automatisch een configuratieklasse van;
- regel 8: we importeren het configuratiebestand van de laag [DAO]. We beschikken dan over alle beans die in dit bestand zijn gedefinieerd;
- regel 9: andere Spring-beans zijn te vinden in de map [elections.metier.service];
14.2.3. Implementatie van de laag [métier]
![]() |
De implementatie van de laag [métier] is dezelfde als die welke in paragraaf 8.5 is gedefinieerd.
14.2.4. De test van de laag [métier]
![]() |
De testklasse is de klasse die in paragraaf 8.6 wordt beschreven.
Te doen: implementeer het project van de laag [métier] en doorloop de unit-test ervan. Genereer het archief van de laag in de lokale Maven-repository (run as/ Maven / install).
14.3. Het Eclipse-project van de laag [web]
![]() |
De weblaag is een Spring-laag MVC:
![]() |
Het Eclipse-project heeft de volgende structuur:
![]() | ![]() |
- [Boot.java] is de klasse die de webservice start;
- [WebConfig.java] is de configuratieklasse van de webservice;
- [Response.java] is het antwoord dat wordt gegenereerd door de verschillende URL-klassen van de webservice;
- [ElectionsController] is de implementatieklasse van de webservice;
14.4. Maven-configuratie
Het project is een Maven-project dat is geconfigureerd via het volgende bestand [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-webjson-metier-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-webjson-metier-dao-spring-data</name>
<description>couche métier exposée comme un service web / jSON</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- bedrijfslaag -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
</dependency>
<!-- laag MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- regels 19-23: de afhankelijkheid van het archief van de laag [métier]. Dit is het archief dat we in paragraaf 14 hebben aangemaakt;
- regels 25-28: de afhankelijkheid voor een Spring-applicatie MVC;
14.5. Spring-configuratie
![]() |
De klasse [WebConfig] configureert de webservice:
package elections.webjson.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.metier.config.MetierConfig;
@EnableWebMvc
@Import({ MetierConfig.class })
@ComponentScan({ "elections.webjson.service" })
public class WebConfig {
// -------------------------------- configuratie van de [web]-laag
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// mapper jSON
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- De betekenis van deze configuratie is uitgelegd in paragraaf 13.5.3.1. We lichten alleen de nieuwe elementen toe:
- regel 22: het configuratiebestand van de laag [métier] wordt geïmporteerd om gebruik te kunnen maken van alle beans daarin;
- regel 23: er wordt aangegeven dat er nog andere beans te vinden zijn in de map [elections.webjson.server.service];
14.6. De startklasse van de webservice
![]() |
De klasse [Boot] start de webservice als volgt:
package elections.webjson.boot;
import org.springframework.boot.SpringApplication;
import elections.webjson.config.WebConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(WebConfig.class, args);
}
}
- regel 10: de statische methode [SpringApplication.run] zal het configuratiebestand [WebConfig] gebruiken. Vanwege de annotatie [@EnableAutoConfiguration] zal Spring Boot de Tomcat-server starten en de webservice daarop implementeren;
14.7. Het antwoord van de webservice URL
![]() |
Alle URL van de webservice / jSON sturen hetzelfde type antwoord:
package elections.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- eigenschappen
// status van de bewerking
private int status;
// eventuele foutmeldingen
private List<String> messages;
// de inhoud van het antwoord
private T body;
// constructors
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters en setters
...
}
Deze klasse is in paragraaf 13.5.5.3 besproken en onderzocht.
14.8. De implementatie van de webservice / jSON
![]() |
De webservice / jSON wordt geïmplementeerd door de volgende klasse [ElectionsController]:
package elections.webjson.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.metier.service.IElectionsMetier;
@Controller
public class ElectionsController {
// Spring-afhankelijkheden
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private IElectionsMetier metier;
@RequestMapping(value = "/getElectionsConfig", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getElectionsConfig() throws JsonProcessingException {
// antwoord
Response<ElectionsConfig> response;
try {
response = new Response<>(0, null,
new ElectionsConfig(metier.getNbSiegesAPourvoir(), metier.getSeuilElectoral()));
} catch (ElectionsException e1) {
response = new Response<>(e1.getCode(), e1.getErreurs(), null);
} catch (RuntimeException e2) {
response = new Response<>(1000, getErreursForException(e2), null);
}
// antwoord
return jsonMapper.writeValueAsString(response);
}
@RequestMapping(value = "/getListesElectorales", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getListesElectorales() throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/setListesElectorales", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String setListesElectorales(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/calculerSieges", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String calculerSieges(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
// privé-methoden -----------------------------
// lijst met foutmeldingen van een RuntimeException
private List<String> getErreursForException(Exception e) {
// de lijst met foutmeldingen van de uitzondering wordt opgehaald
Throwable cause = e;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// de melding wordt alleen opgehaald als deze !=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;
}
}
Opdracht: vul, in navolging van wat in paragraaf 13.5.5 is gedaan, de code van de klasse [ElectionsController] aan.
Opmerkingen:
- er zijn hier geen jSON-filters omdat de tabellen [CONF] en [LISTES] niet via een vreemde-sleutelrelatie met elkaar zijn gekoppeld, wat de code van de webservice aanzienlijk vereenvoudigt;
- vergeet niet de verschillende benodigde Spring-annotaties toe te voegen;
- we geven de URL de naam van de bijbehorende methoden;
- de methode [setListeElectorales] wordt aangeroepen met een bewerking [POST]. De verzonden waarde is de array van concurrerende lijsten (van het type ListeElectorale[]) met hun attributen [sieges, voix, elimine], die in de database moeten worden opgeslagen. Deze methode retourneert een type [Response<Void>] met een veld [status=0] als er geen fout is opgetreden, anders iets anders;
- de methode [calculerSieges] wordt aangeroepen met een bewerking [POST]. De verzonden waarde is de array van de deelnemende lijsten (van het type ListeElectorale[]) met hun attributen [nom, voix]. Deze methode retourneert een type [Response<ListeElectorale[]>] met als inhoud de kieslijsten met hun geïnitialiseerde velden [sieges, elimine];
14.9. Tests
Nadat u de webservice hebt gestart, voert u de volgende tests uit om te controleren of de webservice correct werkt met het hulpprogramma [Advanced Rest Client]:
![]() |
Het antwoord jSON op het vorige verzoek is als volgt: [1]:
![]() |
1 ![]() | 2 ![]() |
Kopieer in [2] het antwoord naar het klembord en plak dit vervolgens in een willekeurige teksteditor [3]:
![]() |
Isoleer de waarde van het veld [body] en wijzig bijvoorbeeld de stemmen van de lijsten. Hieronder [4], worden de stemmen van alle lijsten op 100 gezet:
![]() |
Controleer of uw tekenreeks jSON begint met [ et se termine par ]. Deze tekens dienen om een tabel jSON af te bakenen. Plak in [5] de bovenstaande tekenreeks jSON. Dit wordt de waarde die wordt verzonden voor de volgende URL. Hiervoor moet de methode HTTP [POST] [7] worden geselecteerd.
![]() |
- in [6], vraag dan de URL [setListesElectorales] op. Deze URL wordt opgevraagd met een POST. De ingevoerde waarde is de tabel jSON met de wedstrijdslijsten waarvan de resultaten in de database moeten worden opgeslagen;
Het volgende resultaat wordt verkregen:
![]() |
Het veld [status=0] geeft aan dat er geen fout is opgetreden. Om dit te controleren, vraagt u de wedstrijdlijsten opnieuw op en controleert u of de wijzigingen die u in de lijsten had aangebracht, zijn verwerkt:
![]() |
We voeren opnieuw een [POST] uit om de behaalde zetels per lijst te berekenen:
![]() |
- in [1]: de URL van de zetelberekening;
- naar [2]: we maken een [POST];
- in [3]: de deelnemende lijsten. We vullen het veld [voix] met de waarden van TD, alle [sieges]-waarden zijn 0, alle [elimine]-velden zijn false;
Het verkregen resultaat is als volgt:
![]() |

























