JSON با Jackson (بخش پنجم) استفاده در JAX-RS
در این مقاله کاربرد اصلی Jackson یعنی تولید دادهی متنیِ JSON و ارسال آن در پاسخ به متدهای وبسرویس شرح داده میشود.
در مقالهای با عنوان «مقدمات ساخت سادهترین پروژهی وبسرویس» روش ساخت یک پروژهی سادهی وبسرویس با JAX-RS و پیاده سازی Jersey را شرح دادیم. در بخشهای قبلی نیز، روش تبدیل POJOها به رشتهی با فرمت JSON بررسی شد.
روال کار در نوشتهی حاضر، به این شرح است:
- در گام اول: یک کلاس جدید با نام Student در یک پروژهی وبسرویس که میتواند Hello userName را به کاربر نمایش دهد، میسازیم. در ادامه، به جای متن Hello userName؛ متن تولید شده از ()studentObject.toString را ارسال میکنیم.
- در گام دوم: یک MessageBodyWriter ساخته و وظیفهی ساخت JSON را به آن محول میکنیم.
- در گام سوم: MessageBodyWriterای که توسط Jackson ارائه شده را به کار میبندیم.
- در گام چهارم: از امکانات Jersey برای تبدیل POJO به JSON کمک میگیریم.
پروژهای Maven ای مشابه آنچه در این مقاله شرح داده شد میسازیم. و نام artifactId را simple-jackson-json-jax-rs-provider قرار میدهیم. مطابق شکل زیر، سه پکیج در پروژه ایجاد میکنیم:
در پکیج دوم (dao)، کلاس Student را به شکل زیر تعریف میکنیم:
package com.rhotiz.jsonjaxrs.dao; public class Student { private int id; private String firstName; private String lastName; public Student() { } public Student(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Student(int id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } //getters and setters @Override public String toString() { return "Studnet [firstName=" + firstName + ", lastName=" + lastName + "]"; } }
در پکیج اول (application) کلاس JacksonJaxRsProviderApplication تعریف میشود. ساختار کد به شکل زیر است:
package com.rhotiz.jsonjaxrs.application; import java.util.HashSet; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import com.rhotiz.jsonjaxrs.services.StudentService; @ApplicationPath("") public class JacksonJaxRsProviderApplication extends Application { private Set<Object> singletons = new HashSet<>(); public JacksonJaxRsProviderApplication() { singletons.add(new StudentService()); // singletons.add() other class and features used for JSON production } @Override public Set<Object> getSingletons() { return singletons; } }
در قسمتهای بعدی مقاله، هنگام استفاده از روشهای مختلف ساخت JSON، فقط بخشهایی از متد سازندهی کلاس فوق عوض میشوند. تنها به آوردن بخش تغییر کرده اکتفا خواهیمکرد.
پکیج سوم (services) حاوی متدهای وبسرویس و پیادهسازیهایی از MessageBodyWriter خواهد بود. کلاس مهمی که در این پکیج قرار دارد StudentService است. متدهایی که پاسخگوی درخواستهای GET هستند در این کلاس قرار میگیرند. این کلاس از یک Map به عنوان پایگاهداده استفاده کرده و دو نمونه Student در آن ذخیره میکند. ذخیرهی اشیا در پایگاهداده در سازنده انجام میشود. چون کلاس StudentService، به عنوان Singleton، نمونهسازی (Instantiate) و معرفی میشود، میتوان مطمئن بود که سازندهی این کلاس فقط یک بار اجرا میشود و در طول اجرا، Mapای که از آن به عنوان پایگاهداده استفاده میکنیم، فقط دو نمونه از Student خواهد داشت.
package com.rhotiz.jsonjaxrs.services; import com.rhotiz.jsonjaxrs.dao.Student; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.GET; import javax.ws.rs.Path; @Path("") public class StudentService { private static Map<Integer, Student> studentDB = new TreeMap<>(); private static AtomicInteger index = new AtomicInteger(0); public StudentService() { //Constructor Student student1 = new Student(index.incrementAndGet(), "John", "Doe"); studentDB.put(student1.getId(), student1); Student student2 = new Student(index.incrementAndGet(), "Jane", "Roe"); studentDB.put(student2.getId(), student2); } // web service methods }
گام اول: بازیابی داده به شکل رشته معمولی
هدف از این بخش آشنایی با MessageBodyWriter است.
الف – متد وبسرویس
ابتدا متد زیر را به کلاس StudentService اضافه میکنیم.
//imports import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; // @GET @Path("studentInformationInString/{id}") @Produces(MediaType.TEXT_PLAIN) public Response returnStudentInformationInString(@PathParam("id") int id) { Student student = studentDB.get(id); if (student==null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { return Response.ok().entity(student).build(); } }
در صورتی که متد GET روی URL زیر اجرا شود، متد فوق مورد استفاده قرار خواهد گرفت:
http://localhost:8080/simple-jackson-json-jax-rs-provider/studentInformationInString/anIntNumber
متد فوق، id را از URL استخراج کرده و نمونهی Studentای در دیتابیس که این id را دارد، بازیابی میکند. در صورتی که چنین idای در دیتابیس وجود نداشته باشد Exceptionای پرتاب میشود که توسط JAX-RS به صفحهی مناسب تبدیل شده و به client نشان داده شود، در غیر این صورت Responseای ساخته شده و student را به عنوان بدنهی آن ارسال میکنیم.
ب – کلاس تولید کننده متن
محتویات Produces@ مشخص میکند، student باید، در قالب متن معمولی ارسال شود. تبدیل object ورودی به entity، به نوع دادهای که در Produces@ ذکر شده، بر عهدهی MessageBodyWriter هاست. در نتیجه کلاس زیر را به پکیج services اضافه میکنیم.
package com.rhotiz.jsonjaxrs.services; import com.rhotiz.jsonjaxrs.dao.Student; import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; @Provider @Produces(MediaType.TEXT_PLAIN) public class StudentMessageBodyWriterAsString implements MessageBodyWriter<Student> { @Override public long getSize(Student student, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4) { return 0; } @Override public boolean isWriteable(Class<?> type, Type arg1, Annotation[] arg2, MediaType arg3) { //System.out.println("isWritable evaluated"); return (type == Student.class); } @Override public void writeTo(Student student, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4, MultivaluedMap<String, Object> arg5, OutputStream outputStream) throws IOException, WebApplicationException { //System.out.println("StudentMessageBodyWriterAsString is Called"); outputStream.write(student.toString().getBytes()); } }
این کلاس از سه متد تشکیل شده است. متد اول getSize است و طول رشتهی پاسخ را محاسبه میکند. مقدار صفر را برمیگردانیم تا JAX-RS به صورت اتوماتیک طول رشته را محاسبه نماید.
متد دوم مشخص میکند MessageBodyWriter چه نوع Object هایی را میتواند تبدیل به رشته کند. کد نوشته شده در متد به این مفهوم است که هر شیءای که نمونه ای از کلاس Student باشد توسط این کلاس قابل تبدیل به Plain text است.
متد سوم (writeTo) وظیفهی نوشتن شیء در outputStream جواب را دارد.
ج-تغییرات کلاس Application
در صورتی که بخواهیم StudentMessageBodyWriterAsString (کلاس فوق) برای تبدیل، مورد استفاده قرار گیرد، سازنده موجود در JacksonJaxRsProviderApplication باید به شکل زیر تغییر پیدا کند.
public JacksonJaxRsProviderApplication() { singletons.add(new StudentService()); singletons.add(new StudentMessageBodyWriterAsString());//<<<< important }
د – اجرا
نتیجه ی اجرای وب اپلیکیشن روی سرور و سپس مراجعه به URL زیر در شکل نشان داده شده است:
http://localhost:8080/simple-jackson-json-jax-rs-provider/studentInformationInString/1
در قدم بعدی توانایی Jackson را وارد متد writeTo خواهیم کرد تا رشته در قالب JSON ارسال شود.
گام دوم: بازیابی داده JSON با MessageBodyWriter
الف – ماژول Maven
برای اجرای مثال این بخش، dependency زیر باید به فایل pom.xml اضافه شود.
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-2-version}</version> </dependency>
ب – متد وبسرویس
متد زیر را به کلاس StudentService اضافه می کنیم.
@GET @Path("studentInformationInJSON/{id}") @Produces(MediaType.APPLICATION_JSON) // <<< important public Response returnStudentInformationInJSON(@PathParam("id") int id) { Student student = studentDB.get(id); if (student==null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } else { return Response.ok().entity(student).build(); } }
ج – کلاس تولید کنندهی متن
کلاس MessageBodyWriterAsJSON را در پکیج services میسازیم: این کلاس مشابه MessageBodyWriterAsString بوده و تنها بخش متفاوت متد زیر است:
@Provider @Produces(MediaType.APPLICATION_JSON) public class StudentMessageBodyWriterAsJSON implements MessageBodyWriter<Student> { // other Methods same as StudentMessageBodyWriterAsString @Override public void writeTo(Student student, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { ObjectMapper objectMapper = new ObjectMapper(); //<< objectMapper.writeValue(entityStream, student); //<< } }
د – تغییرات کلاس Application
همان طور که تکه کد فوق نشان میدهد، از ObjectMapper برای تبدیل POJO به JSON استفاده کردیم. به منظور معرفی کلاس فوق، سازنده کلاس JacksonJaxRsProviderApplication باید به شکل زیر تغییر پیدا کند.
public JacksonJaxRsProviderApplication() { singletons.add(new StudentService()); singletons.add(new StudentMessageBodyWriterAsJSON()); // << important }
ه – اجرا
نتیجهی اجرای این وب اپلیکیشن روی سرور و مراجعه به URL زیر در شکل نشان داده شده است:
http://localhost:8080/simple-jackson-json-jax-rs-provider/studentInformationInJSON/2
گام سوم: بازیابی داده JSON با Providerهای Jackson
الف – ماژول Maven
وقتی از پیادهسازیِ Jersey از JAX-RS، استفاده میکنیم، دو روش برای استفاده از Jackson وجود دارد. گام سوم و چهارم به این دو روش میپردازند. روش اول استفاده از وابستگی زیر در maven است:
<dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>${jackson-2-version}</version> </dependency>
در این روش از یکی از کلاس های JacksonJsonProvider یا JacksonJaxbJsonProvider استفاده میشود. این دو، پیادهسازیهایی از MessageBodyWriter و MessageBodyReader اند که شرح آنها در قسمتهای قبل ارائه شد. در صورت استفاده از این روش نیازی به کد نویسی یک MessageBodyWriter جدید نیست، چرا که ماژول Jackson پیادهسازی را انجام داده است؛ تنها آن را نمونهسازی کرده و مورد استفاده قرار میدهیم. (هر چند میتوان تنظیمات مختلفی روی آن اعمال کرد، که جهت رعایت اختصار و سادگی، از آنها عبور میکنیم)
ب – تغییرات کلاس Application
جهت استفاده از این روش باید یک JacksonJsonProvider را به شکل زیر در سازنده معرفی کنیم:
public JacksonJaxRsProviderApplication() { singletons.add(new StudentService()); singletons.add(new JacksonJsonProvider());// <<< important }
ج– اجرا
روش اجرا و نتیجهی آن مانند اجرای گام دوم است.
گام چهارم: بازیابی داده JSON با Featureهای Jersey
الف – ماژول Maven
جهت استفاده از این روش ماژول زیر، از ماژول های Jersey به لیست وابستگیهای Maven اضافه می شود. دقت کنید که ماژول استفاده شده در گام سوم از بستههای Jackson است.
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.5.1</version> </dependency>
jersey-media-json-jackson تنها از یک کلاس تشکیل شده و نام آن JacksonFeature است. این کلاس، Interfaceای به نام javax.ws.rs.core.Feature را پیاده سازی کرده است. Feature تنها یک متد به نام configure دارد. تکه کد زیر بخشی از سورس کد کلاس JacksonFeature است و نشان میدهد این کلاس نیز در پشت صحنه از Provider های Jackson (انچه در گام قبل شرح داده شد) استفاده میکند.
public class JacksonFeature implements Feature { //… @Override public boolean configure(final FeatureContext context) { //… context.register(JacksonJaxbJsonProvider.class,…); //… } //… }
به عبارت دیگر باید گفت وظیفهی معرفی Providerهای Jackson به وباپلیکیشن را، این کلاس بر عهده گرفته است.
ب – تغییرات کلاس Application
در این روش باید یک JacksonFeature را به شکل زیر مورد استفاده قرار دهیم:
public JacksonJaxRsProviderApplication() { singletons.add(new StudentService()); singletons.add(new JacksonFeature());// <<< important }
اشاره به این نکته خالی از فایده نیست که هنگام استفاده از Jersey، معمولا به جای Application، کلاس ResourceConfig را extend میکنند. این کلاس، از Application ارث بری دارد، و برای معرفی resourceها و Featureها و … به فریمورک، متدهای سادهتری در دسترس قرار داده است. با این وجود، جهت حفظ تشابه مثالها، همچنان از کلاس سادهی Application استفاده کردیم.
ج – اجرا
روش اجرا و نتیجهی آن مانند اجرای گام دوم است.
.
.
.
آدرس کانال تلگرام: JavaCupIR@
آدرس اکانت توییتر: JavaCupIR@
آدرس صفحه اینستاگرام: javacup.ir
آدرس گروه لینکدین: Iranian Java Developers