14. [TD]: نمایانگی وب لایه [metier]
کلیدواژهها: معماری چندلایه، Spring، تزریق وابستگی، سرویس وب / jSON، کلاینت/سرور.
بیایید به معماری فعلی برنامه TD بازگردیم:
![]() |
ما قصد داریم این معماری را به شکل زیر تکامل دهیم:
![]() |
به منظور در معرض قرار دادن رابط [IMetier] لایه کسبوکار در وب. برای این کار، از روششناسی توصیفشده در بخش 13.5 پیروی خواهیم کرد.
14.1. Support
![]() |
پروژههای این فصل را میتوان در پوشه [support / chap-14] یافت.
14.2. پروژهٔ Eclipse برای لایهٔ [métier]
![]() |
![]() |
14.2.1. پیکربندی Maven
پروژه لایه [métier] یک پروژه Maven است که توسط فایل زیر [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>
<!-- وابستگیها -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!--لایه [DAO] -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- اسپرینگ بوت -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- آزمایش Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<!-- برای همه چیز از UTF-8 استفاده کنید -->
<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>
- خطوط ۱۸–۲۲: وابستگی به لایه [DAO] که در بخش ۱۲ ساخته شده است؛
- خطوط ۲۳–۳۴: وابستگیهای مورد نیاز برای تست؛
14.2.2. پیکربندی Spring
![]() |
پروژه لایه [métier] یک پروژه Spring است که توسط فایل زیر [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 {
}
- ما در اینجا از نشانه [@Configuration] که کلاس را به عنوان یک کلاس پیکربندی Spring مشخص میکند، استفاده نمیکنیم. حضور anotationهای [@Import, @ComponentScan] به طور خودکار آن را به یک کلاس پیکربندی تبدیل میکند؛
- خط ۸: ما فایل پیکربندی لایه [DAO] را وارد میکنیم. سپس به همهٔ بیینهای تعریفشده در این فایل دسترسی داریم؛
- خط ۹: بیینهای بیشتری از Spring در پوشه [elections.metier.service] یافت میشوند؛
14.2.3. پیادهسازی لایه [métier]
![]() |
پیادهسازی لایه [métier] همانطور که در بخش 8.5 تعریف شده است.
14.2.4. آزمایش لایه [métier]
![]() |
کلاس تست همان است که در بخش 8.6 توصیف شده است.
وظیفه: پروژه لایه [métier] را پیادهسازی کرده و تست واحد آن را با موفقیت پشت سر بگذارید. آرشیو لایه را در مخزن محلی Maven ایجاد کنید (Run As / Maven / Install).
14.3. پروژهٔ Eclipse برای لایهٔ [web]
![]() |
لایه وب یک لایه Spring است MVC:
![]() |
پروژه اکلیپس ساختار زیر را دارد:
![]() | ![]() |
- [Boot.java] کلاسی است که سرویس وب را راهاندازی میکند؛
- [WebConfig.java] کلاس پیکربندی سرویس وب است؛
- [Response.java] پاسخی است که توسط نمونههای مختلف URL از سرویس وب تولید میشود؛
- [ElectionsController] کلاس پیادهسازی سرویس وب است؛
14.4. پیکربندی Maven
این پروژه یک پروژه Maven است که توسط فایل زیر [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>
<!--لایه کسبوکار -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
</dependency>
<!-- لایه 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>
- خطوط ۱۹–۲۳: وابستگی به آرشیو لایه [métier]. این همان آرشیویی است که در پاراگراف ۱۴ ایجاد کردیم؛
- خطوط ۲۵–۲۸: وابستگی برای برنامه Spring MVC;
14.5. پیکربندی Spring
![]() |
کلاس [WebConfig] سرویس وب را پیکربندی میکند:
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 {
// -------------------------------- [web] پیکربندی لایه
@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);
}
// jSON نگاشتکننده
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- هدف این پیکربندی در بخش 13.5.3.1 توضیح داده شد. ما فقط ویژگیهای جدید را توضیح خواهیم داد:
- خط ۲۲: فایل پیکربندی لایه [métier] برای استفاده از تمام بیینهای آن وارد میشود؛
- خط ۲۳: مشخص میکنیم که بیینهای بیشتر در پوشه [elections.webjson.server.service] یافت خواهند شد؛
14.6. کلاس راهاندازی سرویس وب
![]() |
کلاس [Boot] سرویس وب را به شرح زیر راهاندازی میکند:
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);
}
}
- خط ۱۰: متد استاتیک [SpringApplication.run] از فایل پیکربندی [WebConfig] استفاده خواهد کرد. به دلیل انوتیشن [@EnableAutoConfiguration]، Spring Boot سرور Tomcat را راهاندازی کرده و سرویس وب را روی آن مستقر خواهد کرد؛
14.7. پاسخ از سرویس وب URL
![]() |
تمام درخواستهای URL به سرویس وب / jSON همان نوع پاسخ را بازمیگردانند:
package elections.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- ویژگیها
//وضعیت عملیات
private int status;
// هرگونه پیام خطا
private List<String> messages;
// بدنه پاسخ
private T body;
// سازندهها
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// گیرنده و تنظیمکننده
...
}
این کلاس در بخش 13.5.5.3 معرفی و مورد بحث قرار گرفت.
14.8. پیادهسازی سرویس وب / jSON
![]() |
سرویس وب / jSON توسط کلاس زیر [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
@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 {
// پاسخ
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);
}
// پاسخ
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");
}
// متدهای خصوصی -----------------------------
// فهرست پیامهای خطا برای یک RuntimeException
private List<String> getErreursForException(Exception e) {
// فهرست پیامهای خطا را برای استثنا بازیابی میکند
Throwable cause = e;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// پیام را تنها در صورتی بازیابی میکند که != null و خالی نباشد
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// علت بعدی
cause = cause.getCause();
}
return erreurs;
}
}
وظیفه: در ادامه آنچه در بخش 13.5.5 انجام شد، کد کلاس [ElectionsController] را تکمیل کنید.
یادداشتها:
- در اینجا هیچ فیلتری از نوع jSON وجود ندارد زیرا جداول [CONF] و [LISTES] توسط یک رابطه کلید خارجی به هم متصل نیستند، که این امر کد سرویس وب را به طور قابل توجهی ساده میکند؛
- annotationهای مختلف مورد نیاز Spring را فراموش نکنید؛
- متدهای URL بر اساس نام متدهای مرتبطشان نامگذاری خواهند شد؛
- متد [setListeElectorales] با یک عملیات [POST] فراخوانی میشود. مقدار ارسالشده آرایهای از لیستهای رقیب (از نوع ListeElectorale[]) به همراه ویژگیهای [sieges, voix, elimine] آنها است که باید در پایگاه داده ذخیره شود. این متد در صورت عدم وقوع خطا، یک نوع [Response<Void>] با یک فیلد [status=0] را بازمیگرداند، در غیر این صورت چیزی دیگر را بازمیگرداند؛
- متد [calculerSieges] با یک عملیات [POST] فراخوانی میشود. مقدار ارسالشده آرایهای از فهرستهای رقیب (از نوع ListeElectorale[]) به همراه ویژگیهای [nom, voix] آنها است. این متد یک نوع [Response<ListeElectorale[]>] را بازمیگرداند که بدنه آن را فهرستهای انتخاباتی با فیلدهای [sieges, elimine] اولیه شده تشکیل میدهند؛
14.9. Tests
پس از راهاندازی سرویس وب، با استفاده از ابزار [Advanced Rest Client]، تستهای زیر را انجام دهید تا اطمینان حاصل شود که سرویس وب به درستی کار میکند:
![]() |
پاسخ jSON به درخواست قبلی به شرح زیر است: [1]:
![]() |
1 ![]() | 2 ![]() |
در [2]، پاسخ را به کلیپبورد کپی کنید، سپس آن را در هر ویرایشگر متنی [3] جایگذاری کنید:
![]() |
مقدار فیلد [body] را جدا کنید و برای مثال، آراء را برای لیستها تغییر دهید. در زیر، در [4]، آراء را برای همه لیستها روی 100 تنظیم میکنیم:
![]() |
اطمینان حاصل کنید که رشته شما jSON با [ et se termine par ] شروع میشود. این کاراکترها برای جدا کردن یک آرایه jSON استفاده میشوند. در [5]، رشته jSON را که در بالا نشان داده شده است، الصاق کنید. این مقدار برای URL بعدی ارسال خواهد شد. برای انجام این کار، باید متد HTTP [POST] [7] را انتخاب کنید.
![]() |
- در [6]، درخواست URL و [setListesElectorales]. این URL با استفاده از POST درخواست میشود. مقدار ارسالشده جدول jSON فهرستهای رقابتی است که نتایج آن باید در پایگاه داده ثبت شود؛
نتیجه زیر به دست آمد:
![]() |
میدان [status=0] نشان میدهد که هیچ خطایی رخ نداده است. برای تأیید این موضوع، فهرست مسابقات را مجدداً درخواست کنید و بررسی کنید که تغییراتی که در فهرستها اعمال کردهاید، در نظر گرفته شدهاند:
![]() |
ما یک [POST] دیگر اجرا میکنیم تا کرسیهای کسبشده توسط فهرستها را محاسبه کنیم:
![]() |
- در [1]: URL از محاسبه تخصیص کرسیها؛
- به [2]: ما یک [POST] را اجرا میکنیم؛
- در [3]: فهرستهای رقیب. میدان [voix] با مقادیر TD تخصیص داده میشود؛ تمام میدانهای [sieges] روی 0 تنظیم میشوند؛ تمام میدانهای [elimine] روی false تنظیم میشوند؛
نتیجهٔ بهدستآمده به شرح زیر است:
![]() |

























