أغاني قديمة عن الشيء الرئيسي. طلبات جافا والصادرة


( توضيح )

أحد التحديات التي يواجهها 99.9٪ من المطورين هي الوصول إلى نقاط النهاية الخاصة بجهات خارجية. يمكن أن يكون كل من واجهات برمجة التطبيقات الخارجية والخدمات الصغيرة "الخاصة بك". الآن الجميع ضرب الخدمات الصغيرة ، نعم. يعد استرداد البيانات أو إرسالها أمرًا بسيطًا ، ولكن في بعض الأحيان يخترعون الدراجات. هل يمكنك تسمية 5 طرق لتنفيذ الاستعلامات في Java (باستخدام المكتبات وبدون)؟ لا - مرحبا بك في القط. هاه؟ تعال وقارن ؛)

0. مقدمة


المهمة التي سنقوم بحلها بسيطة للغاية: نحن بحاجة إلى إرسال طلب GET / POST والحصول على رد يأتي بتنسيق JSON. لكي لا أكتب خدمة مايكرو أخرى أصلية ، سأستخدم مثالًا يوفر مجموعة من نقاط النهاية مع بعض البيانات. تم تبسيط جميع أمثلة التعليمات البرمجية إلى أقصى حد ، ولن تكون هناك حالات صعبة مع رموز المصادقة ورؤوس هنا. فقط POST و GET و GET و POST ، وهكذا 5 مرات أو نحو ذلك.
لذا دعنا نذهب.

1. حل جافا مدمج


سيكون من الغريب إذا لم يتم حل المهمة بدون استخدام مكتبات الطرف الثالث. بالطبع يمكنك ذلك! لكن حزين. حزمة java.net ، وهي HttpURLConnection و URL و URLEnconder.

لإرسال طلب تحصل عليه ، هذا POST ، تحتاج إلى إنشاء كائن URL وفتح اتصال بناءً على ذلك:

final URL url = new URL("http://jsonplaceholder.typicode.com/posts?_limit=10"); final HttpURLConnection con = (HttpURLConnection) url.openConnection(); 

بعد ذلك ، تحتاج إلى تحسين الاتصال بجميع المعلمات:

 con.setRequestMethod("GET"); con.setRequestProperty("Content-Type", "application/json"); con.setConnectTimeout(CONNECTION_TIMEOUT); con.setReadTimeout(CONNECTION_TIMEOUT); 

واحصل على InputStream ، حيث يمكنك قراءة جميع البيانات المستلمة.

  try (final BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { String inputLine; final StringBuilder content = new StringBuilder(); while ((inputLine = in.readLine()) != null) { content.append(inputLine); } return content.toString(); } catch (final Exception ex) { ex.printStackTrace(); return ""; } 

وفي الواقع ، نحصل هنا على مثل هذه الإجابة (ستكون هي نفسها لجميع الأمثلة اللاحقة ، لأننا نعمل بنفس نقاط النهاية):

 [ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" }, ... { "userId": 1, "id": 9, "title": "nesciunt iure omnis dolorem tempora et accusantium", "body": "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas" }, { "userId": 1, "id": 10, "title": "optio molestias id quia eum", "body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error" } ] 

في حالة طلب POST ، يكون كل شيء أكثر تعقيدًا. لا نريد فقط تلقي إجابة ، ولكن أيضًا نقل البيانات. لهذا نحن بحاجة لوضعهم هناك. تخبرنا الوثائق أن هذا قد يعمل على النحو التالي:

 final Map<String, String> parameters = new HashMap<>(); parameters.put("title", "foo"); parameters.put("body", "bar"); parameters.put("userId", "1"); con.setDoOutput(true); final DataOutputStream out = new DataOutputStream(con.getOutputStream()); out.writeBytes(getParamsString(parameters)); out.flush(); out.close(); 

حيث getParamsString هي طريقة بسيطة تقوم بتعيين الخريطة إلى سلسلة تحتوي على أزواج القيمة الرئيسية:

 public static String getParamsString(final Map<String, String> params) { final StringBuilder result = new StringBuilder(); params.forEach((name, value) -> { try { result.append(URLEncoder.encode(name, "UTF-8")); result.append('='); result.append(URLEncoder.encode(value, "UTF-8")); result.append('&'); } catch (final UnsupportedEncodingException e) { e.printStackTrace(); } }); final String resultString = result.toString(); return !resultString.isEmpty() ? resultString.substring(0, resultString.length() - 1) : resultString; } 

عند الإنشاء الناجح ، نستعيد الكائن:

 { "title": "foo", "body": "bar", "userId": "1", "id": 101} 

مرجع للمصدر الذي يمكنك تشغيله.

2. أباتشي HttpClient


إذا ابتعدنا عن الحلول المدمجة ، فإن أول شيء نواجهه هو HttpClient من Apache. للوصول ، نحن بحاجة إلى ملف JAR نفسه. أو ، بما أنني أستخدم Maven ، فإن التبعية المقابلة هي:

  <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> 

والطريقة التي تبدو بها الاستعلامات باستخدام HttpClient هي بالفعل أفضل بكثير ( المصدر ):

  final CloseableHttpClient httpclient = HttpClients.createDefault(); final HttpUriRequest httpGet = new HttpGet("http://jsonplaceholder.typicode.com/posts?_limit=10"); try ( CloseableHttpResponse response1 = httpclient.execute(httpGet) ){ final HttpEntity entity1 = response1.getEntity(); System.out.println(EntityUtils.toString(entity1)); } final HttpPost httpPost = new HttpPost("http://jsonplaceholder.typicode.com/posts"); final List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("title", "foo")); params.add(new BasicNameValuePair("body", "bar")); params.add(new BasicNameValuePair("userId", "1")); httpPost.setEntity(new UrlEncodedFormEntity(params)); try ( CloseableHttpResponse response2 = httpclient.execute(httpPost) ){ final HttpEntity entity2 = response2.getEntity(); System.out.println(EntityUtils.toString(entity2)); } httpclient.close(); 

حصلنا على نفس البيانات ، لكننا كتبنا نصف الشفرة. أتساءل أين يمكن أن يقودوا عمليات البحث في مثل هذه القضية الأساسية على ما يبدو؟ لكن أباتشي لديه وحدة أخرى تحل مشكلتنا.

3. واجهة برمجة تطبيقات Apache Fluent


  <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.6</version> </dependency> 

وباستخدام Fluent Api ، أصبحت مكالماتنا أكثر قابلية للقراءة ( المصدر ):

  final Content getResult = Request.Get("http://jsonplaceholder.typicode.com/posts?_limit=10") .execute().returnContent(); System.out.println(getResult.asString()); final Collection<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("title", "foo")); params.add(new BasicNameValuePair("body", "bar")); params.add(new BasicNameValuePair("userId", "1")); final Content postResultForm = Request.Post("http://jsonplaceholder.typicode.com/posts") .bodyForm(params, Charset.defaultCharset()) .execute().returnContent(); System.out.println(postResultForm.asString()); 

وكمكافأة - مثال. إذا أردنا نقل البيانات إلى الجسم ليس كنموذج ، ولكن بصفته JSON المفضل لدى الجميع:

  final Content postResult = Request.Post("http://jsonplaceholder.typicode.com/posts") .bodyString("{\"title\": \"foo\",\"body\":\"bar\",\"userId\": 1}", ContentType.APPLICATION_JSON) .execute().returnContent(); System.out.println(postResult.asString()); 

في الواقع ، انهارت المكالمات في سطر واحد من التعليمات البرمجية. بالنسبة لي ، هذا أكثر ودية تجاه المطورين من الطريقة الأولى.

4. الربيع RestTemplate


ماذا بعد ذلك؟ جلبت لي المزيد من الخبرة في عالم الربيع. وليس من المستغرب أن يكون لدى الربيع أيضًا أدوات لحل مهمتنا البسيطة (غريب ، أليس كذلك؟ مهمة ، ولا حتى ذلك - حاجة! - على المستوى الأساسي ، ولكن لسبب ما هناك أكثر من حل واحد). والحل الأول (الأساسي) الذي ستجده في نظام الربيع البيئي هو RestTemplate. ولهذا نحتاج إلى سحب جزء كبير من حديقة الحيوان بأكملها. لذلك إذا كنت بحاجة إلى إرسال طلب في تطبيق NONSpring ، فمن الأفضل عدم سحب المطبخ بأكمله. وإذا كان هناك ربيع بالفعل ، فلماذا لا؟ كيف تسحب كل ما تحتاجه لهذا ، يمكنك أن ترى هنا . حسنًا ، يبدو طلب GET باستخدام RestTemplate في الواقع كما يلي:

  final RestTemplate restTemplate = new RestTemplate(); final String stringPosts = restTemplate.getForObject("http://jsonplaceholder.typicode.com/posts?_limit=10", String.class); System.out.println(stringPosts); 

غطاء محرك السيارة. لكن! لا أريد أن أعمل بسلسلة بعد الآن ، لذلك هناك فرصة لتلقي ليس سلاسل ، ولكن كائنات جاهزة نتوقع استلامها! إنشاء كائن نشر:

 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Builder; import lombok.Getter; import lombok.Setter; @Builder @Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) public class Post { private int id; private String title; private String body; private int userId; public String toString() { return String.format("\n id: %s \n title: %s \n body: %s \n userId: %s \n", id, title, body, userId); } } 

هنا:
باني ، أفضل ، أفضل - سكر من لومبوك ، وليس لكتابة كل شيء يدويًا. نعم ، ها هي ، أمي الكسل.
JsonIgnoreProperties - بحيث في حالة تلقي حقول غير معروفة ، لا تصطدم بالخطأ ، ولكن استخدم الحقول التي نعرفها.
حسنًا ، toString ، لإخراج كائناتنا إلى وحدة التحكم ، ويمكن قراءة ذلك. حسنًا ، في الواقع ، يتم تحويل طلبات GET و POST إلى ( مصدر ):

  // Map it to list of objects final Post[] posts = restTemplate.getForObject("http://jsonplaceholder.typicode.com/posts?_limit=10", Post[].class); for (final Post post : posts) { System.out.println(post); } final Post postToInsert = Post.builder() .body("bar") .title("foo") .userId(1) .build(); final Post insertedPost = restTemplate.postForObject("http://jsonplaceholder.typicode.com/posts", postToInsert, Post.class); System.out.println(insertedPost); 

ولدينا بالفعل أشياء في أيدينا ، وليس سلسلة نحتاج إلى تحليل أنفسنا.

كول. يمكننا الآن كتابة بعض المغلفات حول RestTemplate بحيث يتم إنشاء الاستعلام بشكل صحيح. لا تبدو سيئة للغاية ، ولكن بالنسبة لي ، لا يزال من الممكن تحسينها. يتم كتابة رمز أقل ، أقل فرصة للخطأ. يعلم الجميع أن المشكلة الرئيسية غالبًا ما تكون PEBCAK (توجد مشكلة بين الكرسي ولوحة المفاتيح) ...

5. ربيع Feign


وهنا تأتي المرحلة Feign ، وهي جزء من Spring Cloud. أولاً ، أضف تبعية Feign إلى بيئة الربيع التي تمت إضافتها بالفعل في وقت سابق:

  <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.5.RELEASE</version> </dependency> 

في الواقع ، كل ما هو مطلوب هو الإعلان عن الواجهة والتوصل إليها مع تعليق صغير جيد. خصوصا هذا النهج سيكون جذابا لأولئك الذين يكتبون وحدات تحكم باستخدام الربيع.

إليك ما يتعين علينا القيام به لإرسال الطلبات من خلال Feign ( المصدر ).

 @FeignClient(name = "jsonplaceholder", url = "http://jsonplaceholder.typicode.com", path = "/posts") public interface ApiClient { @RequestMapping(method = GET, value = "/", consumes = APPLICATION_JSON_VALUE) List<Post> getPosts(@RequestParam("_limit") final int postLimit); @RequestMapping(method = POST, value = "/", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) Post savePost(@RequestBody Post post); } 

الجمال ، أليس كذلك؟ ونعم ، يتم إعادة استخدام نماذج البيانات التي كتبناها لـ RestTemplate بشكل جيد هنا.

6. الخلاصة


لا توجد طريقة أخرى للتنفيذ إلى جانب الطرق الخمسة المقدمة. هذه المجموعة هي فقط انعكاس لتجربة المؤلف بالترتيب الذي التقيت به وبدأت في استخدامه في المشاريع. الآن أستخدم Feign بنشاط ، واستمتع بالحياة وانتظر ظهور شيء آخر أكثر ملاءمة حتى تتمكن من الصراخ في الشاشة "<اسم المكتبة> ، أختار لك!" - وكل شيء جاهز للاستخدام والتكامل. في غضون ذلك ، Feign.

سكرتير خاص باعتبارها واحدة من الطرق "البطيئة" يمكنك أن تنظر في العميل Swagger. ولكن ، كما يقولون ، هناك فارق بسيط. لا يستخدم جميع المطورين Swagger لتوثيق واجهات برمجة التطبيقات الخاصة بهم ، وحتى أقل يفعلون ذلك بجودة عالية بحيث يمكنهم إنشاء العميل واستخدامه بسهولة ، وعدم الحصول على مجموعة حشرية بدلاً من ذلك ، مما سيضر أكثر مما ينفع.

Source: https://habr.com/ru/post/ar423591/


All Articles