JavaRush /وبلاگ جاوا /Random-FA /Mapstruct چیست و چگونه می توان آن را برای تست واحد در برن...
Roman Beekeeper
مرحله

Mapstruct چیست و چگونه می توان آن را برای تست واحد در برنامه های SpringBoot به درستی پیکربندی کرد

در گروه منتشر شد

زمینه

سلام به همه دوستان و خوانندگان عزیزم! قبل از نوشتن مقاله، کمی پیش زمینه... اخیرا در کار با کتابخانه Mapstruct به مشکلی برخوردم که در کانال تلگرامم اینجا به اختصار توضیح دادم . در نظرات، مشکل پست حل شد؛ همکار من از پروژه قبلی در این مورد کمک کرد. Mapstruct چیست و چگونه می توان آن را برای تست واحد در برنامه های SpringBoot به درستی پیکربندی کرد.  قسمت 1 - 1پس از آن، تصمیم گرفتم مقاله ای در این زمینه بنویسم، اما البته نگاه محدودی به آن نخواهیم داشت و ابتدا سعی می کنیم سرعت خود را بالا ببریم، بفهمیم Mapstruct چیست و چرا به آن نیاز است، و با استفاده از یک مثال واقعی، این کار را انجام خواهیم داد. وضعیتی را که قبلاً پیش آمده و چگونگی حل آن را تجزیه و تحلیل کنید. بنابراین، من به شدت توصیه می کنم تمام محاسبات را به موازات خواندن مقاله انجام دهید تا همه چیز را در عمل تجربه کنید. قبل از شروع، در کانال تلگرام من عضو شوید ، من فعالیت های خود را در آنجا جمع آوری می کنم، در مورد توسعه در جاوا و به طور کلی IT فکر می کنم. مشترک شد؟ عالی! خب حالا بریم!

Mapstruct، faq؟

یک مولد کد برای نگاشت سریع نوع bean ایمن. اولین وظیفه ما این است که بفهمیم Mapstruct چیست و چرا به آن نیاز داریم. به طور کلی، می توانید در مورد آن در وب سایت رسمی بخوانید. در صفحه اصلی سایت سه پاسخ برای این سوال وجود دارد: چیست؟ برای چی؟ چگونه؟ ما نیز سعی خواهیم کرد این کار را انجام دهیم:

آن چیست؟

Mapstruct کتابخانه ای است که به نگاشت (نقشه گذاری، به طور کلی، این چیزی است که همیشه می گویند: نقشه، نقشه، و غیره) از اشیاء برخی از موجودیت ها به اشیاء موجودیت های دیگر با استفاده از کد تولید شده بر اساس پیکربندی هایی که از طریق رابط ها توصیف می شوند، کمک می کند.

برای چی؟

در بیشتر موارد، ما برنامه های چند لایه (لایه ای برای کار با پایگاه داده، لایه ای از منطق تجاری، لایه ای برای تعامل برنامه با دنیای خارج) توسعه می دهیم و هر لایه دارای اشیاء خاص خود برای ذخیره و پردازش داده ها است. . و این داده ها باید با انتقال از یک موجود به موجودیت دیگر از لایه ای به لایه دیگر منتقل شوند. برای کسانی که با این رویکرد کار نکرده‌اند، ممکن است کمی پیچیده به نظر برسد. به عنوان مثال، ما یک موجودیت برای پایگاه داده Student داریم. هنگامی که داده های این موجودیت به لایه منطق تجاری (خدمات) می رود، باید داده ها را از کلاس Student به کلاس StudentModel منتقل کنیم. بعد، پس از تمام دستکاری‌هایی که در منطق کسب‌وکار انجام شد، داده‌ها باید در خارج منتشر شوند. و برای این ما کلاس StudentDto را داریم. البته باید داده ها را از کلاس StudentModel به StudentDto منتقل کنیم. نوشتن با دست در هر بار روش هایی که منتقل می شود کار فشرده ای است. به علاوه این کد اضافی در پایه کد است که باید حفظ شود. شما می توانید اشتباه کنید. و Mapstruct چنین روشهایی را در مرحله کامپایل تولید می کند و آنها را در منابع تولید شده ذخیره می کند.

چگونه؟

استفاده از حاشیه نویسی ما فقط باید یک حاشیه نویسی ایجاد کنیم که دارای حاشیه نویسی اصلی Mapper باشد که به کتابخانه می گوید که روش های موجود در این رابط را می توان برای ترجمه از یک شی به شی دیگر استفاده کرد. همانطور که قبلاً در مورد دانش آموزان گفتم، در مورد ما این رابط StudentMapper خواهد بود که چندین روش برای انتقال داده از یک لایه به لایه دیگر خواهد داشت:
public class Student {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentDTO {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentModel {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}
برای این کلاس‌ها یک نقشه‌بردار ایجاد می‌کنیم (از این پس این همان چیزی است که ما رابط می‌نامیم، که توضیح می‌دهد چه چیزی و کجا می‌خواهیم انتقال دهیم):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
زیبایی این رویکرد این است که اگر نام و نوع فیلدها در کلاس‌های مختلف یکسان باشد (مانند مورد ما)، تنظیمات Mapstruct برای ایجاد پیاده‌سازی لازم بر اساس رابط StudentMapper در مرحله کامپایل کافی است. ترجمه خواهد کرد. بنابراین از قبل واضح تر شده است، درست است؟ بیایید جلوتر برویم و از یک مثال واقعی برای تجزیه و تحلیل کار در یک برنامه Spring Boot استفاده کنیم.

نمونه ای از کارکرد Spring Boot و Mapstruct

اولین چیزی که نیاز داریم این است که یک پروژه Spring Boot ایجاد کنیم و Mapstruct را به آن اضافه کنیم. برای این موضوع، من یک سازمان در GitHub با قالب هایی برای مخازن دارم و شروعی برای Spring Boot یکی از آنهاست. بر اساس آن، یک پروژه جدید ایجاد می کنیم: Mapstruct چیست و چگونه می توان آن را برای تست واحد در برنامه های SpringBoot به درستی پیکربندی کرد.  قسمت 1 - 2بعد، پروژه را دریافت می کنیم . بله دوستان اگر پروژه را مفید دیدید ستاره بدهید تا بدانم بیهوده این کار را نمی کنم. در این پروژه وضعیتی را که در محل کار دریافت کردم و در پستی در کانال تلگرامم شرح دادیم را فاش خواهیم کرد . من به طور خلاصه وضعیت را برای کسانی که نمی دانند توضیح می دهم: هنگامی که ما آزمایش هایی را برای نقشه نگاران می نویسیم (یعنی برای پیاده سازی های رابطی که قبلاً در مورد آنها صحبت کردیم)، می خواهیم تست ها در سریع ترین زمان ممکن انجام شود. ساده‌ترین گزینه با نقشه‌برداران استفاده از حاشیه‌نویسی SpringBootTest هنگام اجرای تست است که کل ApplicationContext برنامه Spring Boot را انتخاب می‌کند و نگاشت مورد نیاز برای تست را در داخل تست تزریق می‌کند. اما این گزینه منابع فشرده است و زمان بیشتری می برد، بنابراین برای ما مناسب نیست. باید یک تست واحد بنویسیم که به سادگی نگاشت مورد نظر را ایجاد کند و بررسی کند که روش های آن دقیقاً همانطور که انتظار داریم کار می کنند. چرا برای اجرای سریعتر به تست نیاز دارید؟ اگر آزمایش ها طولانی شود، کل روند توسعه را کند می کند. تا زمانی که تست ها روی کد جدید نرسند، نمی توان این کد را صحیح تلقی کرد و برای تست گرفته نمی شود، یعنی وارد تولید نمی شود و این یعنی توسعه دهنده کار را کامل نکرده است. به نظر می رسد، چرا یک آزمایش برای کتابخانه ای بنویسید که عملکرد آن بدون شک است؟ و با این حال، باید یک آزمایش بنویسیم، زیرا ما در حال آزمایش هستیم که چگونه نگاشت را به درستی توصیف کرده‌ایم و آیا آن چیزی را که ما انتظار داریم انجام می‌دهد یا خیر. اول از همه، برای سهولت کار، بیایید Lombok را با افزودن یک وابستگی دیگر به pom.xml به پروژه خود اضافه کنیم:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
در پروژه ما، ما باید از کلاس‌های مدل (که برای کار با منطق تجاری استفاده می‌شوند) به کلاس‌های DTO، که برای برقراری ارتباط با دنیای خارج استفاده می‌کنیم، انتقال دهیم. در نسخه ساده شده ما، فرض می کنیم که فیلدها تغییر نمی کنند و نگاشتهای ما ساده خواهند بود. اما، اگر تمایلی وجود داشته باشد، می توان مقاله مفصل تری در مورد نحوه کار با Mapstruct، نحوه پیکربندی آن و نحوه استفاده از مزایای آن نوشت. اما پس از آن، زیرا این مقاله بسیار طولانی خواهد بود. فرض کنید دانشجویی داریم با لیستی از سخنرانی ها و سخنرانانی که در آنها شرکت می کند. بیایید یک بسته مدل ایجاد کنیم . بر این اساس، یک مدل ساده ایجاد می کنیم:
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
سخنرانی های او
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
و سخنرانان
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
و یک بسته dto در کنار بسته مدل ایجاد کنید :
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
سخنرانی ها
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
و سخنرانان
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
حال بیایید یک نقشه نگار ایجاد کنیم که مجموعه ای از مدل های سخنرانی را به مجموعه ای از سخنرانی های DTO تبدیل کند. اولین کاری که باید انجام دهید اضافه کردن Mapstruct به پروژه است. برای انجام این کار، از وب سایت رسمی آنها استفاده خواهیم کرد ، همه چیز در آنجا توضیح داده شده است. یعنی ما باید یک وابستگی و یک پلاگین به حافظه خود اضافه کنیم (اگر در مورد چیستی حافظه سؤالی دارید، به اینجا بروید، Article1 و Article2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
و در حافظه در بلوک <build/>. که ما هنوز نداشتیم:
<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.5.1</version>
           <configuration>
               <source>1.8</source>
               <target>1.8</target>
               <annotationProcessorPaths>
                   <path>
                       <groupId>org.mapstruct</groupId>
                       <artifactId>mapstruct-processor</artifactId>
                       <version>1.4.2.Final</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
بعد، بیایید یک پکیج mapper در کنار dto و مدل ایجاد کنیم . بر اساس کلاس هایی که قبلا نشان دادیم، باید پنج نقشه نگار دیگر ایجاد کنید:
  • Mapper LectureModel <-> LectureDTO
  • Mapper List<LectureModel> <-> List<LectureDTO>
  • Mapper LecturerModel <-> LecturerDTO
  • Mapper List<LecturerModel> <-> List<LecturerDTO>
  • Mapper StudentModel <-> StudentDTO
برو:

LectureMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LecturerDTO dto);
}

LectureListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LectureMapper.class)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}

LecturerMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LectureDTO dto);
}

LecturerListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LecturerModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LecturerMapper.class)
public interface LecturerListMapper {
   List<LecturerModel> toModelList(List<LecturerDTO> dloList);
   List<LecturerDTO> toDTOList(List<LecturerModel> modelList);
}

StudentMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.StudentDTO;
import com.github.romankh3.templaterepository.springboot.model.StudentModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
public interface StudentMapper {
   StudentDTO toDTO(StudentModel model);
   StudentModel toModel(StudentDTO dto);
}
به طور جداگانه لازم به ذکر است که در نقشه نگارها به نقشه برداران دیگر اشاره می کنیم. این کار از طریق فیلد use در حاشیه نویسی Mapper انجام می شود، همانطور که در StudentMapper انجام می شود:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
در اینجا ما از دو نقشه‌بردار برای ترسیم درست فهرست سخنرانی‌ها و فهرست سخنرانان استفاده می‌کنیم. حال باید کد خود را کامپایل کنیم و ببینیم چه چیزی و چگونه وجود دارد. این کار را می توان با استفاده از دستور mvn clean compile انجام داد . اما، همانطور که مشخص شد، هنگام ایجاد پیاده‌سازی Mapstruct از نقشه‌برداران ما، پیاده‌سازی‌های نگاشت، فیلدها را بازنویسی نکردند. چرا؟ معلوم شد که برداشتن حاشیه نویسی داده از Lombok امکان پذیر نیست. و باید کاری انجام می شد... بنابراین، ما یک بخش جدید در مقاله داریم.

پیوند Lombok و Mapstruct

پس از چند دقیقه جستجو، مشخص شد که باید Lombok و Mapstruct را به روش خاصی به هم متصل کنیم. اطلاعاتی در مورد این در اسناد Mapstruct وجود دارد . پس از بررسی مثال ارائه شده توسط توسعه دهندگان از Mapstruct، اجازه دهید pom.xml خود را به روز کنیم: بیایید نسخه های جداگانه اضافه کنیم:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
بیایید وابستگی گمشده را اضافه کنیم:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
و بیایید افزونه کامپایلر خود را به روز کنیم تا بتواند Lombok و Mapstruct را به هم متصل کند:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.5.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
       <annotationProcessorPaths>
           <path>
               <groupId>org.mapstruct</groupId>
               <artifactId>mapstruct-processor</artifactId>
               <version>${org.mapstruct.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok-mapstruct-binding</artifactId>
               <version>${lombok-mapstruct-binding.version}</version>
           </path>
       </annotationProcessorPaths>
   </configuration>
</plugin>
بعد از این همه چیز باید درست شود. بیایید دوباره پروژه خود را جمع آوری کنیم. اما کجا می توانید کلاس هایی را که Mapstruct ایجاد کرده است پیدا کنید؟ آنها در منابع تولید شده هستند: ${projectDir}/target/generated-sources/annotations/ Mapstruct چیست و چگونه می توان آن را برای تست واحد در برنامه های SpringBoot به درستی پیکربندی کرد.  قسمت 1 - 3 اکنون که ما آماده هستیم تا متوجه ناامیدی من از پست Mapstruct باشیم، بیایید سعی کنیم آزمایش هایی را برای نقشه نگاران ایجاد کنیم.

ما برای نقشه نگاران خود تست می نویسیم

من یک تست سریع و ساده ایجاد خواهم کرد که یکی از نقشه‌برداران را در موردی که در حال ایجاد یک تست یکپارچه‌سازی هستیم آزمایش می‌کند و نگران زمان تکمیل آن نباشید:

LectureMapperTest

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LectureMapperTest {

   @Autowired
   private LectureMapper mapperUnderTest;

   @Test
   void shouldProperlyMapModelToDto() {
       //given
       LectureModel model = new LectureModel();
       model.setId(11L);
       model.setName("lecture name");

       //when
       LectureDTO dto = mapperUnderTest.toDTO(model);

       //then
       Assertions.assertNotNull(dto);
       Assertions.assertEquals(model.getId(), dto.getId());
       Assertions.assertEquals(model.getName(), dto.getName());
   }

   @Test
   void shouldProperlyMapDtoToModel() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(11L);
       dto.setName("lecture name");

       //when
       LectureModel model = mapperUnderTest.toModel(dto);

       //then
       Assertions.assertNotNull(model);
       Assertions.assertEquals(dto.getId(), model.getId());
       Assertions.assertEquals(dto.getName(), model.getName());
   }
}
در اینجا، با استفاده از حاشیه نویسی SpringBootTest، کل applicationContext را اجرا می کنیم و از آن، با استفاده از حاشیه نویسی Autowired، کلاس مورد نیاز برای آزمایش را استخراج می کنیم. از نظر سرعت و راحتی تست نویسی خیلی خوبه. آزمون با موفقیت می گذرد، همه چیز خوب است. اما ما راه دیگر را می رویم و یک آزمون واحد برای نقشه نگار می نویسیم، به عنوان مثال، LectureListMapper...
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl();

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
از آنجایی که پیاده سازی هایی که Mapstruct ایجاد می کند در همان کلاس پروژه ما هستند، ما به راحتی می توانیم از آنها در آزمایش های خود استفاده کنیم. همه چیز عالی به نظر می رسد - بدون حاشیه نویسی، ما کلاس مورد نیاز خود را به ساده ترین روش ایجاد می کنیم و تمام. اما وقتی تست را اجرا می کنیم، متوجه می شویم که خراب می شود و یک NullPointerException در کنسول وجود خواهد داشت... این به این دلیل است که پیاده سازی نگاشت LectureListMapper به این صورت است:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T21:46:12+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   @Autowired
   private LectureMapper lectureMapper;

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
اگر به NPE (مخفف NullPointerException) نگاه کنیم، آن را از متغیر lectureMapper دریافت می کنیم که معلوم می شود مقداردهی اولیه نشده است. اما در پیاده سازی خود سازنده ای نداریم که بتوانیم با آن متغیر را مقداردهی اولیه کنیم. دقیقاً به همین دلیل است که Mapstruct نقشه‌بردار را به این شکل پیاده‌سازی کرده است! در Spring، می‌توانید به روش‌های مختلفی beans را به کلاس‌ها اضافه کنید، می‌توانید آنها را از طریق یک فیلد به همراه حاشیه‌نویسی Autowired، همانطور که در بالا انجام شد، تزریق کنید، یا می‌توانید آنها را از طریق سازنده تزریق کنید. زمانی که نیاز به بهینه سازی زمان اجرای آزمون داشتم، در چنین موقعیت مشکلی در محل کارم قرار گرفتم. فکر کردم نمیشه کاری کرد و در کانال تلگرامم ریختم. و سپس آنها در نظرات به من کمک کردند و گفتند که امکان شخصی سازی استراتژی تزریق وجود دارد. رابط Mapper دارای یک فیلد injectionStrategy است که فقط نام InjectionStrategy را می‌پذیرد که دو مقدار دارد: FIELD و CONSTRUCTOR . اکنون، با دانستن این موضوع، بیایید این تنظیم را به نقشه‌برداران خود اضافه کنیم؛ من آن را با استفاده از LectureListMapper به عنوان مثال نشان خواهم داد :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
قسمتی را که اضافه کردم به صورت پررنگ برجسته کردم. بیایید این گزینه را برای بقیه اضافه کنیم و پروژه را دوباره کامپایل کنیم تا نقشه‌برداران با یک خط جدید تولید شوند. پس از انجام این کار، بیایید ببینیم که چگونه پیاده‌سازی نگاشت برای LectureListMapper تغییر کرده است (قسمتی که به آن نیاز داریم برجسته شده است):
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T22:25:37+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   private final LectureMapper lectureMapper;

   @Autowired
   public LectureListMapperImpl(LectureMapper lectureMapper) {

       this.lectureMapper = lectureMapper;
   }

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
و اکنون Mapstruct تزریق mapper را از طریق سازنده پیاده سازی کرده است. این دقیقاً همان چیزی است که ما در تلاش برای رسیدن به آن بودیم. اکنون تست ما کامپایل را متوقف می کند، بیایید آن را به روز کنیم و دریافت کنیم:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl(new LectureMapperImpl());

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
حالا، اگر تست را اجرا کنیم، همه چیز همانطور که انتظار می رود کار می کند، زیرا در LectureListMapperImpl ما LectureMapper را که به آن نیاز دارد پاس می کنیم... پیروزی! برای شما سخت نیست، اما من خوشحالم: دوستان، همه چیز مثل همیشه است، در حساب GitHub من ، در حساب تلگرام من مشترک شوید . من در آنجا نتایج فعالیت هایم را پست می کنم، واقعاً چیزهای مفیدی وجود دارد) به ویژه از شما دعوت می کنم به گروه بحث کانال تلگرام بپیوندید . این اتفاق می افتد که اگر کسی یک سوال فنی داشته باشد، می تواند در آنجا پاسخ دهد. این قالب برای همه جالب است، می توانید بخوانید که چه می داند و تجربه کسب کنید.

نتیجه

در بخشی از این مقاله، با محصول ضروری و پرکاربردی مانند Mapstruct آشنا شدیم. ما فهمیدیم که چیست، چرا و چگونه. با استفاده از یک مثال واقعی، احساس کردیم که چه کاری می توان انجام داد و چگونه می توان آن را تغییر داد. ما همچنین نحوه تنظیم تزریق لوبیا از طریق سازنده را بررسی کردیم تا بتوان به درستی نقشه‌برداران را آزمایش کرد. همکاران Mapstruct به کاربران محصول خود اجازه دادند دقیقاً نحوه تزریق نقشه‌برها را انتخاب کنند، که بدون شک از آنها تشکر می‌کنیم. اما، علیرغم این واقعیت که اسپرینگ تزریق دانه ها را از طریق سازنده توصیه می کند، بچه های Mapstruct به طور پیش فرض تزریق را از طریق فیلد تنظیم کرده اند. چرا اینطور است؟ بدون پاسخ. من گمان می کنم ممکن است دلایلی وجود داشته باشد که ما از آنها اطلاعی نداریم، و به همین دلیل آنها این کار را به این طریق انجام دادند. و برای اینکه از آنها بفهمم، یک مشکل GitHub در مخزن محصول رسمی آنها ایجاد کردم.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION