hosseinam
hosseinam
خواندن ۸ دقیقه·۴ سال پیش

آشنایی با Dozer Mapper به وسیله annotation در جاوا

بسم الله الرحمن الرحیم
سلام،
قبل از هر چیز بگم من نوشتنم ضعیفه که بهم خورده نگیرید، دوما مطالبی که مینویسم صرفا چیزهایی هستند که به نظرم اومده مفید هستند و سعی میکنم ترجمه روان از سایت های خوب باشند، قبل هر نوشتار هم منبع رو ذکر میکنم، کد نمونه هم سعی میکنم بنویسم و در گیت هاب میذارم، ممنون میشم اگر به کارتون اومد و مورد پسندتون بود یک ستاره مهمونم کنید :)
خب بریم سراغ توضیحات، این نوشته ترجمه ای از سایت baeldung هست که انصافا مرجع خوبی برای جاوا کارهاست. حالا Dozer Mapper چیست و کی از اون استفاده میکنیم؟ همونطور که از اسم اون مشخصه mapper هست و برای مپ(نگاشت) کردن یک کلاس به روی کلاس دیگه (برای زمانی که نیازه ویژگی های یک کلاس رو از روی ویژگی های کلاس دیگه ای مقداردهی کنیم) ازش استفاده میشه، مثل زمان هایی که داریم یک کلاسی رو از سرور میگیریم و نیاز داریم اونو به کلاسی که خودمون نوشتیم نگاشت کنیم یا به عنوان مثال دیگه موقعی که سیاست هایی برای یکپارچه کردن داریم یا ....
این کتابخونه فقط کار نگاشت بین ویژگی ها رو انجام نمیده بلکه زمان هایی که نیاز باشه کار تبدیل نوع(مثلا تبدیل متغیر long یا حتی String به int) رو هم انجام میده.

همین اول بگم من از maven استفاده میکنم و شما میتونید خودتون کتابخونه dozer رو دانلود کنید و به پروژه اضافه کنید یا از gradle و ... استفاده کنید یا مثل من از maven استفاده کنید؛ در فایل pom.xml وابستگی مورد استفادمونو اضافه میکنیم:

<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>

برای استفاده از آخرین نسخه میتونید از این لینک استفاده کنید.


مثال ساده:

خب با یک مثال ساده شروع میکنیم، فرض کنید اطلاعات یک دانش آموز قراره از سمت سرور برا ما ارسال بشه، برای مثال اول فرض کنید که اسم ویژگی ها در کلاس سمت سرور برابر اسم ویژگی های کلاسی است که لازمش داریم:

public class ServersideStudent { private String name; private String family; private int age; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

به دلیل اینکه از reflection برای پیاده سازی dozer mapper استفاده شده، نوشتن متود سازنده بدون آرگومان ضروری هست. کلاس دانش آموز در سمت کلاینت هم به شکل زیر تعریف شده است:

public class Student { private String name; private String family; private int age; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

برای تست برنامه و پیاده سازی تست کوچیک از برنامه های این آموزش از junit4 استفاده شده است. ابتدا متغیر زیر رو به صورت عمومی تعریف میکنیم و در قسمت Before@ اونو مقدار دهی میکنیم:

DozerBeanMapper dozerMapper; @Before public void setUp() throws Exception { dozerMapper = new DozerBeanMapper(); }

برای تست هم قسمت زیر را اجرا میکنیم:(مقدارش رو مستقیم در خود کلاس Student میریزیم)

@Test public void givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_thenCorrect() { ServersideStudent server = new ServersideStudent(&quothossein&quot, &quotkalateh&quot, 27); Student client = dozerMapper.map(server, Student.class); assertEquals(client.getName(), &quothossein&quot); assertEquals(client.getFamily(), &quotkalateh&quot); assertEquals(client.getAge(), 27); }

با اجرای تست بالا میتونید نتیجه رو ببینید که کلاس دانش آموز به درستی مقدار دهی شده است. همچنین به جای اینکه Student.class رو پاس بدیم میتونیم قبلش شیئی از جنس دانش آموز بسازیم و اونو پاس بدیم تا مقدار دهی بشه.

@Test public void givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_thenCorrect() { ServersideStudent server = new ServersideStudent(&quothossein&quot, &quotkalateh&quot, 27); Student client = new Student(); dozerMapper.map(server, client); assertEquals(client.getName(), &quothossein&quot); assertEquals(client.getFamily(), &quotkalateh&quot); assertEquals(client.getAge(), 27); }



مثال تبدیل نوع:

همونطور که قبلا گفتم اگر جنس متغیرهای کلاسی که میخواهیم نگاشت کنیم متفاوت باشد خود dozer زحمت تبدیل نوع رو میکشه. فرض کنید کلاس سمت سرور به شکل زیر تعریف شده است:

public class ServersideStudent1 { private String name; private String point; //رشته private Double height; //اعشاری // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

کلاس سمت کلاینت نیز به شکل زیر تعریف شده است:

public class Student1 { private String name; private int point; private int height; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

همونطور که مشخصه نام ویژگی ها یکسان است ولی نوعشان متفاوت میباشد. برای اعتبار سنجی نیز از کد زیر برای قسمت تست استفاده میکنیم:

@Test public void givenSourceAndDestWithDifferentFieldTypes_whenMapsAndAutoConverts_thenCorrect() { ServersideStudent1 server = new ServersideStudent1(&quothossein&quot, &quot15&quot, 177.6); Student1 client = dozerMapper.map(server, Student1.class); assertEquals(client.getName(), &quothossein&quot); assertEquals(client.getPoint(), 15); assertEquals(client.getHeight(),177); }



نگاشت سفارشی:

نگاشت ویژگی کلاس هایی که نامشان در سمت سرور متفاوت با نام ویژگی های کلاس در سمت کلاینت است با استفاده از annotation ها کار ساده ای میباشد. فرض کنید کلاس دانش آموز سمت سرور به شکل زیر پیاده شده باشد:

public class ServersideStudent2 { private String name; private String family; private String birthPlace; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

و همچنین کلاس دانش آموز سمت کلاینت به شکل زیر پیاده سازی شده باشد:

public class Student2 { private String name; private String lastName; private String zadgah; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

همانطور که مشخص است دو تا ویژگی با نام های متفاوت وجود دارد، در این مواقع کافیست برای نگاشت از Mapping@ در بالای متود getter یک کلاس (فرقی ندارد کدام سمت باشد) قرار گیرد و در آن نام ویژگی در سمت مقابل که قرار است نگاشت از روی آن انجام شود ذکر شود، در این مثال ما برای پیاده سازی متود getter سمت کلاینت به شکل زیر عمل میکنیم:

@Mapping(&quotfamily&quot) public String getLastName() { return lastName; } @Mapping(&quotbirthPlace&quot) public String getZadgah() { return zadgah; }

برای اعتبار سنجی این موضوع قسمت تست به شکل زیر پیاده سازی شده است:

@Test public void givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect() { ServersideStudent2 server = new ServersideStudent2(&quothossein&quot, &quotkalateh&quot, &quottehran&quot); Student2 client = dozerMapper.map(server, Student2.class); assertEquals(client.getName(), server.getName()); assertEquals(client.getLastName(), server.getFamily()); assertEquals(client.getZadgah(), server.getBirthPlace()); }

این کتابخانه به شکل دو سویه پیاده سازی شده است، یعنی وقتی میخواهیم اشیای ساخته شده را سمت سرور بفرستیم نیاز نیست که در سمت مقابل هم Mapping@ داشته باشد، برای درک بیشتر متود تست زیر بیانگر این موضوع است:

@Test public void givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_thenCorrect() { Student2 client = new Student2(&quothossein&quot, &quotkalateh&quot, &quottehran&quot); ServersideStudent2 server = dozerMapper.map(client, ServersideStudent2.class); assertEquals(server.getName(), client.getName()); assertEquals(server.getFamily(), client.getLastName()); assertEquals(server.getBirthPlace(), client.getZadgah()); }



نگاشت API به صورت سفارشی:

راه جایگزین دیگر برای پیاده سازی مثال قبل، استفاده از نگاشت API است. همچنین در این حالت میتوان محدودیت های بیشتری نیز اعمال کرد، برای مثال فرض کنید به دلایلی، نباید مقدار نام از سمت سرور مقدار دهی شود، در ادامه با جزئیات به این حالت میپردازیم. فرض کنید در سمت سرور کلاس زیر موجود باشد:

public class ServersideStudent3 { private String name; private String family; private String birthPlace; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

و همچنین کلاس دانش آموز سمت کلاینت به شکل زیر پیاده سازی شده باشد:

public class Student3 { private String name; private String lastName; private String zadgah; // standard getter and setter @ToString //@AllArgsConstructor //@NoArgsConstructor }

در این حالت از کلاس BeanMappingBuilder برای سفارشی سازی استفاده میکنیم، که به شکل زیر تعریف شده است:

BeanMappingBuilder builder = new BeanMappingBuilder() { @Override protected void configure() { mapping(ServersideStudent3.class, Student3.class) .fields(&quotfamily&quot, &quotlastName&quot) .fields(&quotbirthPlace&quot, &quotzadgah&quot) .exclude(&quotname&quot); } };

همانطور که مشخص هست این کلاس یک متود انتزاعی دارد که به هنگام ساخت شئ باید پیاده سازی شود. سپس مقادیری که قراره تغییر نام داده شوند مشخص میکنیم(ویژگی هایی که در هر دو سمت نام یکسانی دارند، اگر تعیین نشوند نیز مقدارشان به درستی نگاشت میشود)؛ همچنین مقادیری که به دلایلی دوست نداریم روی آن ویژگی نگاشت صورت گیرد با استفاده از متود exclude. تعیین میکنیم.

@Test public void givenApiMapper_whenMaps_thenCorrect() { dozerMapper.addMapping(builder); ServersideStudent3 server = new ServersideStudent3(&quothossein&quot, &quotkalateh&quot, &quottehran&quot); Student3 client = dozerMapper.map(server, Student3.class); assertNull(client.getName()); assertEquals(client.getLastName(), server.getFamily()); assertEquals(client.getZadgah(), server.getBirthPlace()); }

این روش نیز مانند روش های قبلی به صورت دو سویه عمل میکند، یعنی نیاز نیس برای تبدیل شئ کلاینت به کلاس سمت سرور، تنظیمات مجدد و جداگانه ای انجام دهیم.

  • همچنین در این دو مثال آخر گفتیم که اگر نام ویژگی ها در سمت کلاینت متفاوت باشند چیکار کنیم، اما اگر نوع متفاوتی باشند (برای مثال مقدار تاریخ در یک سمت به صورت Long تعریف شده باشد و در سمت دیگر به صورت Date نیاز داشته باشیم) در این صورت باید کلاسی برای مدیریت و تبدیل این ویژگی ها تعریف کنیم و با استفاده از روش XML این کلاس را برای تبدیل نوع مشخص کنیم که در پست بعدی برای این کار مثال می آوریم.

همانطور که اول این پست گفتم، کدها رو در گیت قرار دادم، ممنون میشم در صورت رضایت، ستاره بدین.

javaannotation
شاید از این پست‌ها خوشتان بیاید