جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


پروتکل بافر یک زبان مستقل از پلتفرم برای Serialization و Deserialization دیتای ساختارمند مورد استفاده قرار میگیرد که از روش های XML و JSON سریعتر و ساده تر و کم حجم تر بر پایه باینری میباشد که استفاده از آن در REST API را در این بخش بررسی خواهیم کرد 




نگاهی به Protocol Buffer :


برای استفاده از protocol buffer نیاز داریم که ساختار Message را در قالب فایلهای proto تعریف کنیم و هر فایل معرف دیتایی است که قرار است از یک node به node دیگر منتقل و یا ذخیره شود. 

یک نمونه از فایل proto با نام baeldung.proto را در زیر میبینید که در مسیر src/main/resources نگهداری شده است که در ادامه این بخش از آن استفاده خواهیم کرد :


*در این مثال ما از زبان و کامپایلر protocol buffer ورژن 3 استفاده میکنیم


syntax = "proto3";
package baeldung;
option java_package = "com.baeldung.protobuf";
option java_outer_classname = "BaeldungTraining";
 
message Course {
    int32 id = 1;
    string course_name = 2;
    repeated Student student = 3;
}
message Student {
    int32 id = 1;
    string first_name = 2;
    string last_name = 3;
    string email = 4;
    repeated PhoneNumber phone = 5;
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }
    enum PhoneType {
        MOBILE = 0;
        LANDLINE = 1;
    }
}


در ابتدا باید تعریف کنیم که سینتکس پیام protocol buffer چه ورژنی دارد تا کامپایلر بتواند آنرا تشخیص دهد که در ورژن 2 نیازی به تعریف این خط نیست و بعد پکیج را تعریف کردیم که namespace این پیام را مشخص میکند و از تداخل با بقیه پیام ها هم جلوگیری میکند 

دو خط بعدی تعاریف مورد استفاده در جاوا هستند که پکیج برای تولید کلاس ها استفاده میشود و java_outer_classname نام کلاس را مشخص میکند



بعد از تعریف ساختار یک message ما نیاز به یک کامپایلری داریم که این زبان مستقل از پلتفرم را به کد جاوا تبدیل کند برای دستیابی به کامپایلر مناسب با پلتفرم و نصب آن میتوانید از توضیحات داکیومنت آن استفاده کنید 

و یا میتوانید سورس آنرا از مخزن Maven با جستجوی  com.google.protobuf:protoc بدست آورید و آنرا به باینری کاپایل کنید 


سپس میتوانید نسخه باینری کامپایلر را به مسیر src/main پروژه انتقال داده و فایل های proto را به کلاس های جاوا تبدیل کنیم :


protoc --java_out=java resources/baeldung.proto


این دستور کلاس جاوا با نام BaeldungTraining و پکیج com.baeldung.protobuf را از فایل proto ایجاد میکند 

علاوه بر کامپایلر ما نیاز به Protocol Buffer Runtime داریم که به پروژه اضافه کنیم که ما در اینجا نسخه جاوای آنرا نیاز داریم و به pom اضافه میکنیم :


<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.0.0-beta-3</version>
</dependency>



با استفاده از کامپایلر فایل های proto به کلاس داخلی (Nested Class) کامپایل می شوند. در مثال بالا قسمت Course و Student به کلاس های Course و Student و Message Field ها به فیلد های کلاس همراه با setter و getter تبدیل می شوند

شماره های جلوی فیلدها یک برچسب منحصربفرد است که برای تبدیل شدن به فرم باینری از آنها استفاده میشود 

در ادامه نحوه تبدیل شدن فیلد های درون پیام به فیلد های کلاس را بررسی میکنیم 

از Course شروع میکنیم که شامل دو فیلد ساده id از نوع int و course_name از نوع String است که بعد از کامپایل شدن متد های getter آن به صورت زیر تعریف میشوند :


public int getId();
public java.lang.String getCourseName(); 


** نکته مهمی که وجود دارد در نام گذاری فیلد ها باید دقت کنیم که نام را با حروف کوچک و برای جدا کردن نامهای ترکیبی فیلد ها از underscore باید استفاده کرد که موقع کامپایل camel case بودن انرا بتواند تشخیص و پیاده سازی کند 


فیلد سوم از Course یک نوع پیچیده است که در پایین آن تعریف شده است این فیلد توسط کلمه کلیدی repeated تعریف شده است که به معنای آن است که ممکن است به هر تعداد از آن تکرار شود پس آن فیلد باید در یک مجموعه تعریف شود :


public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student> getStudentList();
public int getStudentCount();
public com.baeldung.protobuf.BaeldungTraining.Student getStudent(int index);




حالا میرویم سراغ Student :


فیلد های ابتدایی آن ساده هستند 


public int getId();
public java.lang.String getFirstName();
public java.lang.String getLastName();
public java.lang.String.getEmail();


فیلد آخر از Student پیچیدگی بیشتری دارد، از نوع repeated است پس شامل متد های زیر میشود :


public java.util.List<com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber> getPhoneList();
public int getPhoneCount();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneNumber getPhone(int index);


همینطور این فیلد یک Nested Type بنام PhoneNumber دارد که خود شامل دو فیلد است :


public java.lang.String getNumber();
public com.baeldung.protobuf.BaeldungTraining.Student.PhoneType getType();


که باز فیلد داخلی PhoneType از نوع enum است که به فرم زیر کامپایل میشود :


public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum {
    MOBILE(0),
    LANDLINE(1),
    UNRECOGNIZED(-1),
    ;
    // Other declarations
}





تا اینجا کمی با سینتکس و ساختار Protocol Buffer آشنا شدیم حالا ببینیم چطور میتوان از آن در Spring REST API استفاده کنیم :



ابتدا باید یک Bean در کلاس اصلی SpringBootApplication@ تعریف کنیم :

@SpringBootApplication
public class Application {
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }
 
    @Bean
    public CourseRepository createTestCourses() {
        Map<Integer, Course> courses = new HashMap<>();
        Course course1 = Course.newBuilder()
          .setId(1)
          .setCourseName("REST with Spring")
          .addAllStudent(createTestStudents())
          .build();
        Course course2 = Course.newBuilder()
          .setId(2)
          .setCourseName("Learn Spring Security")
          .addAllStudent(new ArrayList<Student>())
          .build();
        courses.put(course1.getId(), course1);
        courses.put(course2.getId(), course2);
        return new CourseRepository(courses);
    }
 
    // Other declarations
}



در ابتدا یک Bean از نوع ProtobufHttpMessageConverter داریم که برای تبدیل کردن Response های متدهای RequestMapping@ به پیام های Protocol Buffer استفاده میشود


Bean بعدی CourseRepository نام دارد که برای تست دیتا بکار گرفته شده


*نکته ای که وجود دارد ما با ویژگی های Protocol Buffer کار کرده ایم و نه یا کلاس های POJO




کلاس CourseRepository دارای بدنه ساده ای به شکل زیر دارد :


public class CourseRepository {
    Map<Integer, Course> courses;
     
    public CourseRepository (Map<Integer, Course> courses) {
        this.courses = courses;
    }
     
    public Course getCourse(int id) {
        return courses.get(id);
    }
}


ست کردن تنظیمات Controller :


@RestController
public class CourseController {
    @Autowired
    CourseRepository courseRepo;
 
    @RequestMapping("/courses/{id}")
    Course customer(@PathVariable Integer id) {
        return courseRepo.getCourse(id);
    }
}



*کلاس Course یک DTO است که از Controller ما برگشت داده میشود POJO نیست 





ساخت REST Client و تست :


حالا موقع آن است که پیام های دریافتی از سمت سرور را Deserialization کنیم که دو روش داریم :


روش اول استفاده از RestTemplate API و Bean از پیش طراحی شده ProtobufHttpMessageConverter است که اتوماتیک پیام ها را تبدیل کند


روش دوم استفاده از protobuf-java-format برای تبدیل پیام ها به JSON بصورت دستی است 




کلاس ApplicationTest را برای تست گرفتن آماده میکنیم :


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest
public class ApplicationTest {
    // Other declarations
}


که کد های زیر در این کلاس قرار میگیرند 


URL ارتباطی را تعریف میکنیم :

private static final String COURSE1_URL = "http://localhost:8080/courses/1";


و بعد از دریافت پاسخ آنرا با کد زیر تست میگیریم :


private void assertResponse(String response) {
    assertThat(response, containsString("id"));
    assertThat(response, containsString("course_name"));
    assertThat(response, containsString("REST with Spring"));
    assertThat(response, containsString("student"));
    assertThat(response, containsString("first_name"));
    assertThat(response, containsString("last_name"));
    assertThat(response, containsString("email"));
    assertThat(response, containsString("john.doe@baeldung.com"));
    assertThat(response, containsString("richard.roe@baeldung.com"));
    assertThat(response, containsString("jane.doe@baeldung.com"));
    assertThat(response, containsString("phone"));
    assertThat(response, containsString("number"));
    assertThat(response, containsString("type"));
}




تست با RestTemplate :


@Autowired
private RestTemplate restTemplate;
 
@Test
public void whenUsingRestTemplate_thenSucceed() {
    ResponseEntity<Course> course = restTemplate.getForEntity(COURSE1_URL, Course.class);
    assertResponse(course.toString());
}


برای اینکه بتوانیم با RestTemplate تست بگیریم نیاز است که یک Bean را در آن ثبت کنیم تا عملیات تبدیل را انجام دهد :


@Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
    return new RestTemplate(Arrays.asList(hmc));
}


کلاس ProtobufHttpMessageConverter نقش تبدیل کننده پیام ها را بر عهده دارد که توسط RestTemplate بکار گرفته شده





تست بوسیله HttpClient :


در این روش بصورت دستی پیام های protocol buffer را تبدیل میکنیم ابتدا نیاز است کتابخانه های مورد نیاز را اضافه کنیم :


<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>


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


protobuf-java-format


HttpClient


 حالا با کد زیر درخواست GET ارسال کرده و پاسخ را در قالب InputStream تبدیل میکنیم :


private InputStream executeHttpRequest(String url) throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet(url);
    HttpResponse httpResponse = httpClient.execute(request);
    return httpResponse.getEntity().getContent();
}


حال InputStream که حاوی پیام Protocol Buffer است را به JSON تبدیل میکنیم :


private String convertProtobufMessageStreamToJsonString(InputStream protobufStream) throws IOException {
    JsonFormat jsonFormat = new JsonFormat();
    Course course = Course.parseFrom(protobufStream);
    return jsonFormat.printToString(course);
}



و حالا تست میگیرین :


@Test
public void whenUsingHttpClient_thenSucceed() throws IOException {
    InputStream responseStream = executeHttpRequest(COURSE1_URL);
    String jsonOutput = convertProtobufMessageStreamToJsonString(responseStream);
    assertResponse(jsonOutput);
}



محتوای JSON ای که تبدیل کردیم مشابه زیر خواهد بود :


id: 1
course_name: "REST with Spring"
student {
    id: 1
    first_name: "John"
    last_name: "Doe"
    email: "john.doe@baeldung.com"
    phone {
        number: "123456"
    }
}
student {
    id: 2
    first_name: "Richard"
    last_name: "Roe"
    email: "richard.roe@baeldung.com"
    phone {
        number: "234567"
        type: LANDLINE
    }
}
student {
    id: 3
    first_name: "Jane"
    last_name: "Doe"
    email: "jane.doe@baeldung.com"
    phone {
        number: "345678"
    }
    phone {
        number: "456789"
        type: LANDLINE
    }
}







نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی