17. در دسترس قرار دادن یک پایگاه داده در وب
17.1. معماری سرویس وب /jSON
ما معماری زیر را پیادهسازی خواهیم کرد:
![]() |
- در [1]، لایههای [DAO, [JPA], JDBC] توسط یکی از ۲۴ پیکربندی ارائهشده در فصلهای قبلی، بهویژه در بند ۱۵؛ پیادهسازی میشوند؛
- لایه [DAO] [3] کلاینت راه دور، همان رابط را پیادهسازی میکند که لایه [DAO] [1] پیادهسازی کرده است، و این به ما اجازه میدهد تا از همان لایه آزمایشی فصلهای قبلی استفاده کنیم. گویی لایههای [2-3] برای لایه [4] شفاف هستند؛
ما از پروژههای زیر استفاده خواهیم کرد:
- پروژه [sgbd-config-jdbc] که لایه JDBC را در یکی از شش پروژه SGBD پیکربندی میکند؛
- پروژه [sgbd-config-jpa-*] که لایه JPA از پروژه SGBD را که برای یکی از سه پیادهسازی JPA در نظر گرفته شده است، پیکربندی میکند (Hibernate, EclipseLink, OpenJpa);
- پروژهٔ عمومی [spring-jdbc-04]، که لایههای [DAO] و [1] را پیادهسازی میکند؛
- پروژهٔ عمومی [spring-jpa-generic]، که لایههای [DAO] و [2] را پیادهسازی میکند؛
- پروژه عمومی [spring-webjson-server-jdbc-generic]، که یک سرویس وب مبتنی بر پروژه [spring-jdbc-04] را پیادهسازی میکند؛
- پروژه عمومی [spring-webjson-server-jpa-generic]، که یک سرویس وب مبتنی بر پروژه [spring-jpa-generic] را پیادهسازی میکند؛
- کلاینت عمومی [spring-webjson-client-generic]، که تنها کلاینت برای تمام ۲۴ پیکربندی سرویس وب خواهد بود؛
17.2. راهاندازی محیط کار
ما با موارد زیر کار خواهیم کرد:
- SGBD MySQL 5.6.25;
- JPA پیادهسازی Hibernate؛
پروژههای زیر را به STS وارد کنید:
![]() |
- پروژههای [spring-webjson-*] در پوشه [<exemples>\spring-database-generic\spring-webjson] یافت میشوند؛
- [Alt-F5] را اجرا کرده و سپس تمام پروژههای فوق را مجدداً تولید کنید؛
برای بررسی اینکه محیط کاری بهدرستی نصب شده است، مراحل زیر را انجام دهید:
- وب سرویس را با استفاده از پیکربندی زمان اجرای [spring-webjson-server-jpa-generic-hibernate] که مبتنی بر پیادهسازی JPA / Hibernate است، راهاندازی کنید؛
![]() | ![]() |
سپس:
- کلاینت این سرویس وب را با استفاده از پیکربندی زمان اجرا [spring-webjson-client-generic] راهاندازی کنید، که آزمایشی از JUnit است:
![]() | ![]() |
آزمون باید با موفقیت انجام شود:
![]() |
- در [1]، سرویس وب را متوقف کنید و سپس با استفاده از پیکربندی اجرای [spring-webjson-server-jdbc-generic] که بر اساس پیادهسازی JDBC است، سرویس وب را راهاندازی کنید:
![]() | ![]() |
سپس کلاینت این سرویس وب را با استفاده از پیکربندی زمان اجرا [spring-webjson-client-generic] راهاندازی کنید:
![]() | ![]() |
آزمون باید با موفقیت انجام شود:
![]() |
17.3. پیادهسازی سرویس وب / jSON / JDBC
ابتدا به معماری زیر نگاه میکنیم:
![]() |
جایی که لایه [DAO] [1] مستقیماً با لایه JDBC از SGBD ارتباط برقرار میکند.
17.3.1. پروژه Eclipse برای سرویس وب
پروژه Eclipse برای سرویس وب / jSON / JDBC به شرح زیر است:
![]() |
این یک پروژه 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>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-webjson-server-jdbc-generic</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!--لایه وب -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--لایه [DAO] -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- افزونهها -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- خطوط ۱۱–۱۵: پروژهٔ والد Maven؛
- خطوط 24–28: وابستگی به لایه [DAO / JDBC] که توسط پروژه [spring-jdbc-generic-04] پیادهسازی شده است؛
- خطوط ۱۹–۲۲: وابستگی به آرتیفکت [spring-boot-starter-web]. این آرتیفکت شامل تمام وابستگیهای لازم برای ایجاد یک سرویس وب / jSON است. همچنین شامل کتابخانههای غیرضروری نیز میباشد. بنابراین پیکربندی دقیقتری لازم است، اما این پیکربندی برای شروع کار مفید است.
وابستگیهای گنجاندهشده در این پیکربندی به شرح زیر است:
![]() | ![]() | 1 ![]() |
- در [1]، میتوانیم ببینیم که Eclipse وابستگی به آرشیو پروژه [spring-jdbc-generic-04] را تشخیص داده است؛
وابستگیهای فهرستشده در بالا برای هر دو لایه [DAO] و [web] اعمال میشوند.
17.3.2. پیکربندی لایه [web]
لایه [web] توسط دو فایل پیکربندی Spring پیکربندی شده است:
![]() |
17.3.2.1. کلاس [WebConfig]
هدف اصلی کلاس [WebConfig] پیکربندی است:
- سرور Tomcat که سرویس وب بر روی آن مستقر خواهد شد؛
- فیلترهای jSON برای سریالیسازی و دِسریالیسازی اشیاء [Produit] و [Categorie]:
package spring.webjson.server.config;
import java.util.List;
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.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- پیکربندی لایه [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("", 8081);
}
// -------------------------------- پیکربندی فیلتر [json]
...
}
- خط ۲۵: این کلاس یک کلاس پیکربندی Spring است؛
- خط 26: anotation [@EnableWebMvc] نشان میدهد که لایه وب با استفاده از Spring MVC پیادهسازی شده است. این کار پیکربندیهای ضمنی را فعال میکند که نیازی به راهاندازی آنها نداریم؛
- خطوط ۳۰–۳۱: تزریق کانکست اسپرینگ برنامه؛
- خطوط 33–37: تعریف بیون [dispatcherServlet]، که در برنامههای Spring MVC، بهعنوان یک [FrontController] عمل میکند، وظیفهاش مسیریابی درخواستهای کلاینت به کنترلری است که قادر به پردازش آنهاست؛
- خطوط ۳۹–۴۲: سرویسلت (servlet) سرویس وب به همراه URL که آن را مدیریت میکند، ثبت میشود. در اینجا، [/*] مشخص شده است که به تمام URLs اشاره دارد؛
- خطوط ۴۴–۴۷: تعریف bean [embeddedServletContainerFactory] که سرور وب مورد استفاده را مشخص میکند. در اینجا، این سرور وب Tomcat [http://tomcat.apache.org/] خواهد بود. سرور Jetty [http://www.eclipse.org/jetty/] نیز میتواند استفاده شود. هر دو سرور، سرورهای تعبیهشدهای هستند که در وابستگیهای Maven گنجانده شدهاند. هنگامی که [Spring Boot] شروع به راهاندازی پروژه میکند، بهطور خودکار سرور وب مشخصشده در پیکربندی را راهاندازی کرده و سرویس یا برنامه وب را روی آن مستقر میکند؛
فیلترهای jSON به شرح زیر پیکربندی شدهاند:
package spring.webjson.server.config;
import java.util.List;
...
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- پیکربندی لایه [web]
...
// -------------------------------- پیکربندی فیلتر [json]
// نقشهبرداری jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// فیلترها jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
}
- خط ۸: کلاس [WebConfig] از کلاس [WebMvcConfigurerAdapter] ارث میبرد. کلاس دوم، برنامه وب را با مقادیر پیشفرض پیکربندی میکند. برای سفارشیسازی این پیکربندی، باید برخی از متدهای این کلاس را بازنویسی کرد. در اینجا، ما میخواهیم متد [configureMessageConverters] را از کلاس [22-26] (توجه کنید به تگ @Override) که یک لیست از «تبدیلکنندهها» را تعریف میکند، بازنویسی کنیم. سرویس وب /jSON و کلاینت آن خطوط متنی را مبادله میکنند. یک مبدل ابزاری است که قادر است یک شیء را از یک خط متن دریافتشده (دسریالیزاسیون) ایجاد کند و یک خط متن را از یک شیء (سریالیزاسیون) بسازد. در اینجا، خطوط متن رشتههای jSON خواهند بود. بنابراین ما به jSON به عنوان سریالیسازی/دسریالیسازی اشاره خواهیم کرد؛
- خط ۲۳: متد [configureMessageConverters] یک لیست از مبدلها را به عنوان پارامتر میپذیرد؛
- خطوط ۲۴–۲۵: مبدل jSON [MappingJackson2HttpMessageConverter] برای خطوط [14-20] به این لیست اضافه میشود. این کار امکان تبادل jSON بین کلاینت و سرور را فراهم میکند؛
- lines [14-20]: یک مبدل jSON را تعریف میکند که توسط کلاس [MappingJackson2HttpMessageConverter] پیادهسازی شده است. این کلاس (خط ۱۰) را میتوان در وابستگیهای Maven پروژه یافت؛
- خطوط [17-18]: یک مپر jSON ایجاد شده و به کانورتر [MappingJackson2HttpMessageConverter] اختصاص داده میشود؛
- خطوط [29-32]: مپر jSON را که در خط 17 ایجاد شده است، بهعنوان یک Spring bean تعریف میکند. این کار آن را در کانتکست Spring قرار میدهد و برای تزریق به سایر beans یا استفاده در کد اپلیکیشن وب در دسترس قرار میدهد؛
- خطوط ۳۴–۴۱: یک فیلتر jSON برای مپر قبلی jSON تعریف میکند؛
- خط ۳۵: آناوتیشن [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] تضمین میکند که بین تعریفشده در اینجا از نوع singleton (تکنسلهای) نباشد. هر بار که از کانکست درخواست شود، متد [jsonMapperCategorieWithoutProduits] مجدداً اجرا خواهد شد. این امر در اینجا ضروری است زیرا ما چهار فیلتر jSON را تعریف میکنیم. با این حال، در هر لحظه تنها یکی از آنها باید فعال باشد. با دادن دامنه [ConfigurableBeanFactory.SCOPE_PROTOTYPE] به بین (bean)، ما تضمین میکنیم که متد دوباره اجرا شود و فیلتر قبلی با فیلتر جدید جایگزین گردد؛
- برای درک این فیلترها، مهم است به خاطر داشته باشیم که در لایه [DAO]:
- وجوده [Produit] با حاشیهنویسی [jsonFilterProduit] حاشیهنویسی شده است؛
- ذره [Categorie] با حاشیهنویسی [jsonFilterCategorie] حاشیهنویسی شده است؛
بنابراین فیلترهایی با این نامها باید تعریف شوند.
- خطوط [34-41]: یک فیلتر به نام [jsonMapperShortCategorie] تعریف کنید که نمایش jSON یک دستهبندی بدون محصولات آن را ارائه میدهد؛
- lines [43-51]: یک فیلتر به نام [jsonMapperLongCategorie] تعریف کنید که نمایش jSON یک دستهبندی همراه با محصولاتش را ارائه میدهد؛
- خطوط [53-60]: یک فیلتر به نام [jsonMapperShortProduit] تعریف کنید که نمایش jSON یک محصول بدون دستهبندی آن را ارائه میدهد؛
- lines [62-70]: یک فیلتر به نام [jsonMapperLongProduit] تعریف کنید که نمایش jSON یک محصول را با دستهبندی آن فراهم میکند؛
17.3.2.2. کلاس [AppConfig]
کلاس [AppConfig] کل برنامه را پیکربندی میکند، یعنی لایههای [web] و [DAO]:
package spring.webjson.server.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {
}
- خط ۷: این کلاس یک کلاس پیکربندی Spring است؛
- خط ۹: بیینهای لایه [DAO / JDBC] به همراه آنهایی که توسط کلاس [WebConfig] تعریف شدهاند، وارد میشوند. بنابراین، تمام بیینهای لایه [DAO] در وباپلیکیشن / jSON در دسترس خواهند بود؛
- خط ۸: بستههایی را مشخص میکند که در آنها سایر بیینهای Spring قابل یافتن هستند؛
17.3.3. استثناء [ServerException]
![]() |
همانند فصلهای قبلی، در حالی که لایه [DAO] یک استثنای گرفتهنشده [DaoException] را پرتاب کرد، لایه [web] یک استثنای گرفتهنشده [ServerException] را پرتاب خواهد کرد:
package spring.webjson.server.infrastructure;
import generic.jdbc.infrastructure.UncheckedException;
public class ServerException extends UncheckedException {
private static final long serialVersionUID = 1L;
// سازندگان
public ServerException() {
super();
}
public ServerException(int code, Throwable e, String simpleClassName) {
super(code, e, simpleClassName);
}
}
- خط ۵: کلاس [ServerException] از کلاس [UncheckedException] که در پروژه برای پیکربندی لایه JDBC (خط ۳) تعریف شده است، ارث میبرد؛
17.3.4. کنترلکنندهها
![]() |
![]() |
در اینجا دو کنترلر خواهیم داشت:
- [CategorieController] به پرسوجوهای مربوط به دستهبندیها رسیدگی خواهد کرد؛
- [CategorieController] درخواستهای مربوط به محصولات را مدیریت خواهد کرد؛
متدهای URL که توسط کنترلرها ارائه شدهاند، بهصورت یکبهیک با متدهای رابطهای [DaoCategorie] و [DaoProduit] در لایه [DAO] مطابقت دارند:
![]() | ![]() |
همانطور که در بالا نشان داده شده است:
- متد وب [deleteAllCategories] متد [deleteAllEntities] از کلاس [DaoCategorie] را فراخوانی خواهد کرد؛
- متد وب [getShortCategoriesById] متد [getShortEntitiesById] از کلاس [DaoCategorie] را فراخوانی خواهد کرد؛
همین امر در مورد محصولات نیز صدق میکند:
![]() | ![]() |
17.3.4.1. ابجکتهای URL که توسط کنترلکننده [CategorieController] در معرض دید قرار گرفتهاند
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. URL که توسط کنترلر [ProduitController] افشا شده است
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.5. پیادهسازی عمومی سرویس وب
![]() |
فهرست URL که توسط سرویس وب ارائه شده است نشان میدهد که همان انواع URL برای مدیریت دستهبندیها و محصولات ارائه شدهاند. به جای نوشتن دو کنترلر بسیار مشابه، آنها را از کلاسی مشتق میکنیم که تمام کارهای مشترک بین دو کنترلر را مدیریت خواهد کرد. این کلاس [AbstractController] است که در بالا به آن اشاره شد. این کلاس رابط زیر [Iws] را پیادهسازی خواهد کرد:
package spring.webjson.server.service;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import spring.jdbc.entities.AbstractCoreEntity;
public interface Iws<T extends AbstractCoreEntity> {
// فهرست تمام موجودیتهای T
public Response<List<T>> getAllShortEntities();
public Response<List<T>> getAllLongEntities();
// از نهادهای خاص – نسخهٔ کوتاه
public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
// واحدهای خاص – نسخه طولانی
public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
// بهروزرسانی چندین موجودیت
public Response<List<T>> saveEntities(HttpServletRequest request);
//حذف تمام نهادها
public Response<Void> deleteAllEntities();
// حذف چندین انتیت
public Response<Void> deleteEntitiesById(HttpServletRequest request);
public Response<Void> deleteEntitiesByName(HttpServletRequest request);
}
این رابط، متدهای لایه رابط [DAO] را که مورد استفاده قرار خواهند گرفت، در بر میگیرد:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.AbstractCoreEntity;
public interface IDao<T extends AbstractCoreEntity> {
// فهرست تمام انتیتههای T
public List<T> getAllShortEntities();
public List<T> getAllLongEntities();
// واحدهای خاص – نسخهٔ کوتاه
public List<T> getShortEntitiesById(Iterable<Long> ids);
public List<T> getShortEntitiesById(Long... ids);
public List<T> getShortEntitiesByName(Iterable<String> names);
public List<T> getShortEntitiesByName(String... names);
// واحدهای خاص – نسخه طولانی
public List<T> getLongEntitiesById(Iterable<Long> ids);
public List<T> getLongEntitiesById(Long... ids);
public List<T> getLongEntitiesByName(Iterable<String> names);
public List<T> getLongEntitiesByName(String... names);
// بهروزرسانی چندین موجودیت
public List<T> saveEntities(Iterable<T> entities);
public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
// حذف تمام انتیتها
public void deleteAllEntities();
//حذف چندین موجودیت
public void deleteEntitiesById(Iterable<Long> ids);
public void deleteEntitiesById(Long... ids);
public void deleteEntitiesByName(Iterable<String> names);
public void deleteEntitiesByName(String... names);
public void deleteEntitiesByEntity(Iterable<T> entities);
public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}
انتقال از رابط [IDao<T>] لایه [DAO] به رابط [Iws<T>] سرویس وب، از قوانین زیر پیروی کرده است:
- متدهای رابط [Iws<T>] خطا (exception) ایجاد نخواهند کرد. اگر خطایی رخ دهد، در شیء [Response] محصور خواهد شد؛
- متغیرهای پارامتر مانند موارد موجود در خطوط ۴۵ و ۴۷ ([Iterable<String> names, String... names]) حذف میشوند. متدها پارامترهای خود را از درخواست HTTP کلاینت با نوع [HttpServletRequest request] دریافت میکنند؛
تمام پاسخهای سرویس وب در شی زیر از نوع [Response] قرار خواهند گرفت:
![]() |
package spring.webjson.server.service;
public class Response<T> {
// ----------------- ویژگیها
//وضعیت عملیات
private int status;
// یک پیام خطا
private String exception;
// بدنه پاسخ
private T body;
// سازندهها
public Response() {
}
public Response(int status, String exception, T body) {
this.status = status;
this.exception = exception;
this.body = body;
}
// گیرنده و تنظیمکننده
...
}
- خط ۴: پاسخ شامل یک نوع T است؛
- خط ۱۲: پاسخ از نوع T;
- خطوط ۷–۱۰: یک متد ممکن است با یک استثنا مواجه شود. در این صورت، پاسخ را با: بازخواهد گرداند
- خط ۸: status!=0;
- خط ۱۰: یک پیام خطا؛
کلاس [AbstractController] به شرح زیر است:
package spring.webjson.server.service;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// DAO لایه
private IDao<T> dao;
abstract protected IDao<T> getDao();
// محلی
private String simpleClassName = getClass().getSimpleName();
@PostConstruct
public void init(){
dao=getDao();
}
@Override
public Response<List<T>> getAllShortEntities() {
try {
// پاسخ
return new Response<List<T>>(0, null, dao.getAllShortEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getAllLongEntities() {
try {
// پاسخ
return new Response<List<T>>(0, null, dao.getAllLongEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
@Override
public Response<Void> deleteAllEntities() {
try {
// حذف
dao.deleteAllEntities();
// پاسخ
return new Response<Void>(0, null, null);
} catch (DaoException e) {
return new Response<Void>(1, e.toString(), null);
} catch (Exception e) {
return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
}
}
@Override
public Response<Void> deleteEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
...
}
}
متدها همگی به یک روش پیادهسازی شدهاند:
- اگر به اطلاعات نیاز داشته باشند، آن را از شیء [HttpServletRequest request] بازیابی میکنند؛
- آنها متدی را در لایه [DAO] فراخوانی میکنند که نامش با خودشان یکسان است؛
- آنها هر استثنایی را که ممکن است در عملیات ۱ (استخراج پارامترها) یا در عملیات ۲ (فراخوانی لایه [DAO]) رخ دهد، مدیریت میکنند؛
ابتدا بررسی کنیم که چگونه لایه [DAO] به کلاس [AbstractController] تزریق میشود:
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// لایه DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
@PostConstruct
public void init(){
dao=getDao();
}
- خط ۱: کلاس انتزاعی است و رابط عمومی [Iws<T>] را پیادهسازی میکند؛
- خطوط ۳–۴: تزریق کانکست Spring؛
- خط ۷: ارجاع هنوز ناشناخته به لایه [DAO] که قرار است استفاده شود؛
- خط ۹: متد انتزاعی [getDao]، که مرجع لایه [DAO] را که باید استفاده شود، بازخواهد گرداند. این متد توسط کلاس فرزند بازنویسی (override) خواهد شد، و بنابراین این کلاس فرزند است که مشخص میکند کدام لایه [DAO] باید استفاده شود (DaoProduit یا DaoCategorie);
- خط ۱۱: حاشیهنویسی [@PostConstruct] متدی را مشخص میکند که پس از نمونهسازی شی اجرا شود. پس از تکمیل نمونهسازی، تزریقهای Spring انجام خواهند شد. کلاس فرزند سپس مرجع لایه [DAO] خود را دریافت کرده و در نتیجه قادر خواهد بود آن را به کلاس والد خود پاس دهد؛
متد [getShortEntitiesById] به شرح زیر است:
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
try {
// ارزش ارسالشده را بازیابی کنید
String body = CharStreams.toString(request.getReader());
// آن را از حالت سریال خارج میکند
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
});
// پاسخ
return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
}
}
- خط ۵: مقداری که توسط کلاینت ارسال میشود یک رشته jSON خواهد بود. اینجاست که بازیابی میشود؛
- خطوط ۷–۹: رشته jSON حاوی لیست کلیدهای اصلی برای آن دسته از موجودیتها است که نسخه کوتاه برای آنها مورد نیاز است؛
- خط ۱۱: متد [DAO] با همین نام فراخوانی میشود. پاسخ از نوع [List<T>] در یک شیء [Response] بستهبندی میشود؛
- خط ۱۳: حالتی که لایه [DAO] یک استثنا پرتاب کرده است؛
- خط ۱۵: برای سایر خطاها، بهویژه خطای احتمالی در حین سریالبرداری معکوس پارامتر jSON، خط ۸؛
متد [getShortEntitiesByName] مشابه است:
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
try {
// ارزش ارسالشده را بازیابی میکند
String body = CharStreams.toString(request.getReader());
// آن را سریالبرداری معکوس میکند
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
});
//پاسخ
return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
}
}
- خطوط ۴–۹: در اینجا، پارامتر jSON فهرستی از نامهای دستهبندی است که برای آنها نسخه کوتاه مورد نیاز است؛
متد [saveEntities] پیادهسازی نشده است زیرا کاملاً به ماهیت موجودیتی که باید پایدارسازی شود، [Categorie] یا [Produit]، وابسته است. کد کمی برای بازسازی وجود دارد. بنابراین این کار به کلاسهای فرزند واگذار شده است.
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
17.3.6. کنترلکننده [CategorieController]
![]() |
کنترلکننده [CategorieController] پردازش URL مربوط به دستههای زیر را انجام میدهد:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class CategorieController extends AbstractController<Categorie> {
@Autowired
private IDao<Categorie> daoCategorie;
@Override
protected IDao<Categorie> getDao() {
return daoCategorie;
}
//محلی
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories() {
// پدر
Response<List<Categorie>> response = super.getAllShortEntities();
// فیلترهای سریالی jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllLongCategories() {
// والد
Response<List<Categorie>> response = super.getAllLongEntities();
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
// پدر
Response<List<Categorie>> response = super.getShortEntitiesById(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
// پدر
Response<List<Categorie>> response = super.getShortEntitiesByName(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
// پدر
Response<List<Categorie>> response = super.getLongEntitiesById(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
// پدر
Response<List<Categorie>> response = super.getLongEntitiesByName(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
public Response<Void> deleteAllCategories() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
- خط ۲۶: کلاس [CategorieController] از کلاس [AbstractController] ارث میبرد؛
- خط ۲۵: آناوتیشن [@RestController] این کلاس را بهعنوان یک کامپوننت Spring مشخص میکند. این آناوتیشن همچنین نشان میدهد که این کلاس یک سرویس وب است که متدهای آن پاسخهای خود را مستقیماً در قالب jSON به کلاینت ارسال میکنند؛
- خطوط ۲۸–۲۹: ارجاع به لایه [DAO] در اینجا تزریق میشود؛
- خطوط ۳۱–۳۴: بازتعریف متد [getDao]، که در کلاس والد به صورت انتزاعی (abstract) اعلام شده و هدف آن بازگرداندن مرجعی به لایه [DAO] برای استفاده است؛
تمام متدها بر اساس یک مدل یکسان ساخته شدهاند:
- واگذاری پردازش به کلاس والد؛
- ابتداییسازی مپر jSON که پاسخ را سریالیسازی خواهد کرد؛
- ارسال پاسخ؛
بیایید نگاهی به امضای چند نمونه از URL بیندازیم:
| - URL [/getAllShortCategories] با یک GET فراخوانی میشود |
| - URL [/getShortCategoriesById] با یک POST فراخوانی میشود. مقدار ارسالشده رشته jSON است که شامل کلیدهای اصلی دستهبندیهای مورد نظر؛ |
| - URL [/getLongCategoriesByName] با یک POST. مقدار ارسالشده رشتهای است که jSON حاوی نامهای دستهبندیهای مورد نظر؛ |
اکنون بیایید با جزئیات روش [saveCategories] را بررسی کنیم که از قالب سایر روشها پیروی نمیکند:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// دستهبندیها حفظ میشوند
try {
// بازیابی مقدار ارسالشده
String body = CharStreams.toString(request.getReader());
// دسریالیزهسازی آن
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// حفظ دستهبندیها
categories = daoCategorie.saveEntities(categories);
// نتیجه را بازگردانید
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// نتیجه
return new Response<List<CoreCategorie>>(0, null, coreCategories);
} catch (DaoException e) {
return new Response<List<CoreCategorie>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
}
}
- خط ۱: URL [/saveCategories] با یک مقدار ارسالشده همراه است. این رشته jSON حاوی نامهای کامل دستهبندیهایی است که باید حفظ شوند؛
- خطوط ۵–۱۰: دستهبندیهایی که باید حفظ شوند از رشته jSON بازسازی میشوند. پیوند [produit.categorie]، که یک [Produit] را به [Catégorie] آن متصل میکند، null است زیرا، در نسخه طولانی یک [Categorie]، هر [Produit] در نسخه کوتاه خود بدون فیلد [categorie] خود قرار دارد. این یک مشکل نیست، زیرا لایه [DAO] که با JDBC پیادهسازی شده است، به این اطلاعات نیازی ندارد؛
- خط ۱۲: دستهبندیها ذخیره شدهاند. فهرست دستهبندیهای دریافتشده با کلیدهای اصلی عناصر ذخیرهشده، یعنی دستهبندیها و محصولات، غنیسازی شده است. تغییر دیگری صورت نگرفته است. به جای بازگرداندن کل لیست دریافتشده – که هزینه در بر دارد – ما فقط کلیدهای اصلی عناصر این لیست را بازمیگردانیم. برای این کار، از کلاسهای زیر استفاده میکنیم: [CoreCategorie] و [CoreProduit]:
![]() |
package spring.webjson.server.entities;
import java.util.List;
public class CoreCategorie {
// کلید اصلی
private Long id;
// تولیدکنندگان
public CoreCategorie() {
}
public CoreCategorie(Long id) {
this.id=id;
}
// فهرست محصولات
private List<CoreProduit> coreProduits;
// گیرنده و تنظیمکننده
...
}
- خط ۸: کلید اصلی یک محصول؛
- خط ۲۰: کلیدهای اصلی محصولات آن؛
package spring.webjson.server.entities;
public class CoreProduit {
// کلید اصلی
private Long id;
// سازندهها
public CoreProduit() {
}
public CoreProduit(Long id) {
this.id = id;
}
// گیرندهها و تنظیمکنندهها
...
}
- خط ۶: کلید اصلی یک محصول؛
بیایید به کد متد [saveCategories] بازگردیم:
...
// پایدارسازی دستهبندیها
categories = daoCategorie.saveEntities(categories);
// بازگرداندن نتیجه
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// نتیجه
return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
- خطوط ۵–۱۷: لیست [CoreCategorie] ساخته میشود که به کلاینت راه دور بازگردانده خواهد شد؛
- خط ۱۹: پاسخ بازگردانده شده و به صورت jSON سریالیزه میشود؛
17.3.7. مدیریت فیلتر jSON
برای هر متد یک کنترلکننده، دو نقطه وجود دارد که در آنها سریالیسازی/دسریالیسازی انجام میشود:
- دسریالیزهسازی مقدار ارسالشده: این بخش بهصورت صریح در اینجا انجام میشود؛
- سریالسازی نتیجه: این به طور ضمنی در اینجا انجام میشود؛
بیایید با دِسیریالیزهسازی مقدار ارسالشده در [CategorieController.saveCategories] شروع کنیم:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// ردهبندیها را حفظ میکند
try {
// بازیابی مقدار ارسالشده
String body = CharStreams.toString(request.getReader());
// آن را از حالت سریالی خارج کنید
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
- خط ۸: در زمینه Spring، ما یک mapper را که برای پردازش فیلتر jSON [jsonMapperLongCategorie] پیکربندی شده است، بازیابی میکنیم. بیایید به تعریف این mapper در کلاس پیکربندی [WebConfig] بازگردیم:
// -------------------------------- پیکربندی فیلتر [json]
// نقشهبرداری jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// فیلترها jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
- خطوط ۳۲–۴۰: مپر jSON [jsonMapperLongCategorie] که توسط کلاس [CategorieController] بازیابی شده است؛
- خط ۳۵: این مپر توسط متد [jsonMapper] در خطوط ۱۸–۲۱ بازگردانده میشود؛
- خطوط ۱۸–۲۱: متد [jsonMapper] مپر jSON را از کانورتر [MappingJackson2HttpMessageConverter] در خطوط ۳–۹ رندر میکند؛
به عبارت دیگر، مپر jSON که در خط ۴ زیر در [CategorieController.saveCategories] بازیابی شده است:
// بازیابی مقدار ارسالشده
String body = CharStreams.toString(request.getReader());
// آن را از حالت سریالی خارج میکند
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
تبدیلکنندهای است که بهطور پیشفرض توسط Spring MVC برای دسرالیز کردن مقدار ارسالشده توسط کلاینت و سریالیز کردن نتیجه بازگرداندهشده به آن استفاده میشود. در خطوط بالا، هیچ دسرالیز ضمنی از مقدار ارسالشده وجود نداشت. برای این کار، لازم بود موارد زیر نوشته شود:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {
در این حالت، رمزگشایی خودکار مقدار ارسالشده در پارامتر [categories] انجام میشد. با این حال، مشکل فیلتر [jsonFilterCategorie] که توسط اجزای [Categorie] استفاده میشود، وجود داشت. این مورد نیاز به پیکربندی دارد. به همین دلیل ما به دسریالیزاسیون صریح (خطوط ۴–۵) روی آوردیم. نکته دوم این است که مپر در خط ۴ (که بهصورت پیشفرض توسط Spring MVC استفاده میشود) برای سریالیز کردن نتیجه [Response<List<CoreCategorie>] نیز مناسب است. در واقع، عنصر [CoreCategorie] فاقد فیلتر jSON است. بنابراین نیازی به پیکربندی مپر خروجی jSON با یک فیلتر اضافی نیست. در این حالت، پاسخی که به کلاینت ارسال میشود بهطور ضمنی سریالسازی خواهد شد.
17.3.8. کنترلکننده [ProduitController]
![]() |
کنترلکننده [ProduitController] پردازش فایلهای URL مربوط به محصولات را انجام میدهد. کد آن مشابه کد کنترلکننده [CategorieController] است:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class ProduitController extends AbstractController<Produit> {
@Autowired
private IDao<Produit> daoProduit;
@Override
protected IDao<Produit> getDao() {
return daoProduit;
}
// محلی
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllShortProduits() {
// والد
Response<List<Produit>> response = super.getAllShortEntities();
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllLongProduits() {
// والد
Response<List<Produit>> response = super.getAllLongEntities();
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
// والد
Response<List<Produit>> response = super.getShortEntitiesById(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
// پدر
Response<List<Produit>> response = super.getShortEntitiesByName(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
// والد
Response<List<Produit>> response = super.getLongEntitiesById(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
// پدر
Response<List<Produit>> response = super.getLongEntitiesByName(request);
// فیلترهای سریالیسازی jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// پاسخ
return response;
}
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
public Response<Void> deleteAllProduits() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
فقط متد [saveProduits] ساختار متفاوتی نسبت به سایر متدها دارد:
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
try {
//مقدار ارسالشده بازیابی میشود
String body = CharStreams.toString(request.getReader());
// آن را از حالت سریالی خارج میکند
ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
});
//محصولات ذخیره میشوند
produits = daoProduit.saveEntities(produits);
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : produits) {
coreProduits.add(new CoreProduit(produit.getId()));
}
//پاسخ را بازمیگرداند
return new Response<List<CoreProduit>>(0, null, coreProduits);
} catch (DaoException e) {
return new Response<List<CoreProduit>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
}
}
- خطوط ۴–۹: از رشته دریافتی jSON، فهرست موارد [Produit] که باید ذخیره شوند، بازسازی میشود. از آنجا که رشته دریافتی jSON با نسخههای کوتاه محصولات مطابقت دارد، فیلد [categorie] برای آنها null است. بار دیگر، لایه DAO / JDBC به این اطلاعات نیازی ندارد؛
- خط ۱۱: محصولات ذخیره میشوند؛
- خطوط ۱۲–۱۵: لیست [CoreProduit] که باید بازگردانده شود، ساخته میشود؛
- خط ۱۸: پاسخ بازگردانده میشود؛ این پاسخ توسط مپر در خط ۷ سریالیزه خواهد شد (سریالیزاسیون ضمنی توسط Spring MVC انجام میشود) قبل از ارسال به کلاینت راه دور (به بحث در بخش 17.3.7 مراجعه کنید)؛
17.3.9. کلاس زمان اجرای سرویس وب / jSON
![]() |
کلاس [Boot] کلاس اجرایی پروژه است:
package spring.webjson.server.boot;
import org.springframework.boot.SpringApplication;
import spring.webjson.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- خط ۱۰: متد استاتیک [SpringApplication.run] اجرا میشود. کلاس [SpringApplication] یک کلاس درون پروژه [spring Boot] (خط ۳) است. دو پارامتر به آن ارسال میشود:
- [AppConfig.class]: کلاسی که کل برنامه را پیکربندی میکند؛
- [args]: هر آرگومانی که به متد [main] در خط 9 ارسال میشود. این پارامتر در اینجا استفاده نمیشود؛
هنگامی که این کلاس اجرا میشود، لاگهای زیر تولید میشوند:
- خطوط ۱۱–۱۵: بیینهایی که فیلترهای jSON را تعریف میکنند، کشف میشوند. آنها بیینهای با نامهای مشابه کشفشده در پروژه پیکربندی JDBC را بازنویسی میکنند؛
- خطوط 17–18: سرور Tomcat راهاندازی میشود که سرویس وب /jSON را اجرا خواهد کرد؛
- خطوط ۱۹–۲۱: کانکست Spring با شناسه MVC راهاندازی میشود؛
- خطوط ۲۴–۴۳: سرویسهای آشکارشده URL کشف میشوند؛
17.3.10. آزمون سرویس وب / jSON
برای انجام آزمایشها، از کلاینت [Advanced Rest Client] (به بخش 23.11 مراجعه کنید) برای پرسوجو از URL که توسط سرویس وب / jSON ارائه شده است، استفاده میکنیم (البته سرویس وب / jSON باید در حال اجرا باشد، و همچنین SGBD). برای پر کردن پایگاه داده، پیکربندی اجرای به نام [spring-jdbc-generic-04-fillDataBase] را اجرا میکنیم که پایگاه داده را با ۵ دستهبندی و ۱۰ محصول پر میکند:
![]() |
![]() |
- در [1-3]، ما از طریق یک دستور برای HTTP و GET، URL و [/getAllLongCategories] را درخواست میکنیم؛
پاسخ زیر را دریافت میکنیم:
![]() |
- در [1]، درخواست کلاینت HTTP;
- به [2]، پاسخ سرور HTTP است؛
- [3]، که در آن وضعیت [200 OK] نشان میدهد که سرور درخواست را با موفقیت پردازش کرده است؛
- در [4]، پاسخ سرور jSON;
پاسخ کامل jSON به شرح زیر است:
{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
- status:0 به این معنی است که هیچ خطایی در سمت سرور وجود نداشته است؛
- exception: null به معنای عدم وجود پیام خطا است؛
- body: بخش اصلی پاسخ است، در این مورد فهرست دستهبندیها به همراه محصولاتشان. دو دستهبندی وجود دارد، هر کدام با ۵ محصول؛
ما قصد داریم محصول [produit15] را به دستهبندی [categorie1] اضافه کنیم. برای انجام این کار، از URL و [/saveProduits] استفاده خواهیم کرد که در انتظار رشته jSON حاوی محصولاتی هستند که باید ذخیره شوند (افزودن/تغییر). این رشته به شرح زیر خواهد بود:
[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]
درخواست به سرویس وب / jSON به شرح زیر ارسال میشود:
![]() |
- در [1]، URL درخواست شده است؛
- در [2]، از طریق عملیاتی POST درخواست میشود؛
- در [3]، رشته jSON ارسال میشود؛
- در [4]، به سرور اطلاع داده میشود که jSON برای آن ارسال خواهد شد؛
پاسخ سرور به شرح زیر است:
![]() |
- در [1]، ما فهرستی از موارد [CoreProduit] را به همراه کلیدهای اصلی آنها دریافت کردیم. در اینجا، ما فهرستی حاوی یک مورد واحد با کلید اصلی محصولی که به تازگی در پایگاه داده وارد کردهایم، دریافت کردیم؛
اکنون، بیایید نسخه کامل دسته با نام [categorie[1]] را بازیابی کنیم:
![]() |
- در [1]، URL درخواستشده؛
- در [2]، یک POST ایجاد میکنیم؛
- در [3,4]، مقدار ارسالشده یک رشته jSON است. این نشاندهنده فهرست نامهای دستهبندی است که برای آنها نسخه طولانی را میخواهیم؛
نتیجهٔ زیر به دست میآید:
![]() |
- در [5]، دستهبندی [produit[1,5]] اکنون یک محصول ششم دارد؛
اکنون این محصول را حذف کنیم:
![]() |
- به [1]، URL درخواستی؛
- به [2]، یک POST ایجاد میکنیم؛
- در [3-4]، یک رشته jSON ارسال میشود که نمایانگر لیست کلیدهای اصلی برای محصولات حذفشده است؛
نتیجهٔ بهدستآمده به شرح زیر است:
![]() |
- [status:0] نشان میدهد که حذف با موفقیت انجام شده است؛
اکنون، بیایید با ارسال پرسوجو برای محصول [body:[0]] بررسی کنیم که آیا واقعاً حذف شده است:
![]() |
نتیجه زیر را دریافت میکنیم:
![]() |
- [status:0] نشان میدهد که عملیات بدون هیچ استثنایی تکمیل شده است؛
- [produit[1,5]] نشان میدهد که [body] یک لیست حاوی 0 عنصر است. بنابراین، موجودیت [[${#httpServletRequest.remoteUser}]] با موفقیت حذف شده است؛
تمام عملیات [GET] را میتوان در یک مرورگر وب استاندارد انجام داد:
![]() |
از خوانندگان دعوت میشود تا سایر عملیات URL سرویس وب / jSON را آزمایش کنند.








































