Alte Lieder über die Hauptsache. Java und ausgehende Anfragen


( Abbildung )

Eine der Herausforderungen für 99,9% der Entwickler ist der Zugriff auf Endpunkte von Drittanbietern. Dies können sowohl externe APIs als auch „Ihre eigenen“ Microservices sein. Jetzt schlagen alle Microservices, ja. Das Abrufen oder Senden von Daten ist einfach, aber manchmal erfinden sie Fahrräder. Können Sie 5 Möglichkeiten nennen, um Abfragen in Java zu implementieren (mit und ohne Bibliotheken)? Nein - willkommen bei Katze. Huh? Komm und vergleiche;)

0. Intro


Die Aufgabe, die wir lösen werden, ist äußerst einfach: Wir müssen eine GET / POST-Anfrage senden und eine Antwort im JSON-Format erhalten. Um keinen weiteren ursprünglichen Microservice zu schreiben, verwende ich ein Beispiel , das eine Reihe von Endpunkten mit einigen Daten bereitstellt. Alle Codebeispiele sind maximal vereinfacht, es wird hier keine kniffligen Fälle mit Authentifizierungstoken und Headern geben. Nur POST und GET, GET und POST usw. ungefähr fünfmal.
Also lass uns gehen.

1. Integrierte Java-Lösung


Es wäre seltsam, wenn die Aufgabe ohne die Verwendung von Bibliotheken von Drittanbietern nicht gelöst werden könnte. Natürlich kannst du! Aber traurig. Das java.net-Paket, nämlich HttpURLConnection, URL und URLEnconder.

Um eine Anfrage an GET, diesen POST zu senden, müssen Sie ein URL-Objekt erstellen und eine darauf basierende Verbindung öffnen:

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

Als nächstes müssen Sie die Verbindung mit allen Parametern aufpeppen:

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

Und holen Sie sich einen InputStream, von dem aus Sie alle empfangenen Daten lesen können.

  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 ""; } 

Und tatsächlich erhalten wir hier eine solche Antwort (sie wird für alle nachfolgenden Beispiele gleich sein, da wir mit denselben Endpunkten arbeiten):

 [ { "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" } ] 

Bei der POST-Anfrage ist alles etwas komplizierter. Wir wollen nicht nur eine Antwort erhalten, sondern auch Daten übertragen. Dafür müssen wir sie dort platzieren. Die Dokumentation sagt uns, dass dies wie folgt funktionieren könnte:

 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(); 

Wobei getParamsString eine einfache Methode ist, die Map to String mit Schlüssel-Wert-Paaren zuordnet:

 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; } 

Nach erfolgreicher Erstellung erhalten wir das Objekt zurück:

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

Ein Verweis auf die Quelle, die Sie ausführen können.

2. Apache HttpClient


Wenn wir uns von den integrierten Lösungen entfernen, stoßen wir zuerst auf Apaches HttpClient. Für den Zugriff benötigen wir die JAR-Datei selbst. Oder, da ich Maven verwende, lautet die entsprechende Abhängigkeit:

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

Und die Art und Weise, wie Abfragen mit HttpClient aussehen, ist bereits viel besser ( Quelle ):

  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(); 

Wir haben die gleichen Daten erhalten, aber halb so viel Code geschrieben. Ich frage mich, wo sonst können sie Suchanfragen in einem so scheinbar grundlegenden Problem durchführen? Aber Apache hat ein anderes Modul, das unser Problem löst.

3. Apache Fluent API


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

Und bereits mit Fluent Api werden unsere Anrufe viel lesbarer ( Quelle ):

  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()); 

Und als Bonus - ein Beispiel. Wenn wir Daten nicht als Formular, sondern als jedermanns Lieblings-JSON an den Körper übertragen möchten:

  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()); 

Tatsächlich wurden die Aufrufe zu einer Codezeile zusammengefasst. Für mich ist dies gegenüber Entwicklern viel freundlicher als die allererste Methode.

4. Spring RestTemplate


Was kommt als nächstes? Weitere Erfahrungen brachten mich in die Welt des Frühlings. Und es überrascht nicht, dass der Frühling auch Werkzeuge hat, um unsere einfache Aufgabe zu lösen (seltsam, richtig? Eine Aufgabe, nicht einmal das - ein Bedürfnis! - auf einer grundlegenden Ebene, aber aus irgendeinem Grund gibt es mehr als eine Lösung). Die erste (grundlegende) Lösung, die Sie im Spring-Ökosystem finden, ist RestTemplate. Und dafür müssen wir einen erheblichen Teil des gesamten Zoos ziehen. Wenn Sie also eine Anfrage in einer NONSpring-Anwendung senden müssen, ist es besser, nicht die gesamte Küche zu ziehen. Und wenn es schon eine Quelle gibt, warum dann nicht? Wie Sie alles ziehen, was Sie dazu brauchen, erfahren Sie hier . Nun, tatsächlich sieht die GET-Anforderung mit RestTemplate folgendermaßen aus:

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

Haube. ABER! Ich möchte nicht mehr mit einer Zeichenfolge arbeiten, umso mehr besteht die Möglichkeit, keine Zeichenfolgen, sondern fertige Objekte zu empfangen, die wir erwarten! Erstellen Sie ein Post-Objekt:

 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); } } 

Hier:
Baumeister, Getter, Setter - Zucker aus Lombok, nicht alles von Hand schreiben. Ja, hier ist sie, Faulheit Mutter.
JsonIgnoreProperties - damit beim Empfang unbekannter Felder kein Fehler auftritt , sondern die uns bekannten Felder verwendet werden.
Nun, toString , um unsere Objekte an die Konsole auszugeben, und dies könnte gelesen werden. Nun, tatsächlich werden unsere GET- und POST-Anfragen in ( Quelle ) umgewandelt:

  // 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); 

Und wir haben bereits Objekte in der Hand und keine Zeichenfolge, die wir selbst analysieren müssen.

Kul. Jetzt können wir einen Wrapper um RestTemplate schreiben, damit die Abfrage korrekt erstellt wird. Es sieht nicht so schlecht aus, aber für mich kann es noch verbessert werden. Je weniger Code geschrieben wird, desto geringer ist die Fehlerwahrscheinlichkeit. Jeder weiß, dass das Hauptproblem häufig PEBCAK (Problem zwischen Stuhl und Tastatur) ist ...

5. Spring Feign


Und hier kommt die Bühne Feign, die Teil von Spring Cloud ist. Fügen Sie zunächst die Feign-Abhängigkeit zu der bereits zuvor hinzugefügten Frühlingsumgebung hinzu:

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

In der Tat ist alles, was benötigt wird, die Schnittstelle zu deklarieren und sie mit einer guten kleinen Anmerkung aufzupeppen. Insbesondere dieser Ansatz wird für diejenigen attraktiv sein, die Steuerungen mit Feder schreiben.

Folgendes müssen wir tun, um Anfragen über Feign ( Quelle ) zu senden.

 @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); } 

Schönheit, nicht wahr? Und ja, die Datenmodelle, die wir für RestTemplate geschrieben haben, werden hier gut wiederverwendet.

6. Fazit


Neben den fünf vorgestellten gibt es keine andere Art der Implementierung. Diese Sammlung spiegelt nur die Erfahrungen des Autors in der Reihenfolge wider, in der ich sie getroffen und in Projekten verwendet habe. Jetzt benutze ich Feign aktiv, genieße das Leben und warte, bis etwas anderes Bequemeres erscheint, damit Sie in den Monitor „<Bibliotheksname>, ich wähle Sie!“ Rufen können. - und alles war einsatzbereit und integrationsbereit. In der Zwischenzeit Feign.

PS Als eine der "faulen" Möglichkeiten können Sie den generierten Client Swagger betrachten. Aber wie sie sagen, gibt es eine Nuance. Nicht alle Entwickler verwenden Swagger, um ihre APIs zu dokumentieren, und noch weniger in einer so hohen Qualität, dass sie den Client problemlos generieren und verwenden können und stattdessen keine entomologische Sammlung erhalten, die mehr schadet als nützt.

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


All Articles