De vieilles chansons sur l'essentiel. Java et requêtes sortantes


( Illustration )

L'un des défis rencontrés par 99,9% des développeurs est d'accéder à des points de terminaison tiers. Il peut s'agir à la fois d'API externes et de «vos propres» microservices. Maintenant, tout le monde utilise des microservices, oui. Récupérer ou envoyer des données est simple, mais parfois ils réinventent la roue. Pouvez-vous nommer 5 façons d'implémenter des requêtes en Java (à l'aide de bibliothèques et sans)? Non - bienvenue au chat. Hein? Venez comparer;)

0. Intro


La tâche que nous allons résoudre est extrêmement simple: nous devons envoyer une demande GET / POST et obtenir une réponse au format JSON. Afin de ne pas écrire un autre microservice original, j'utiliserai un exemple qui fournit un ensemble de points de terminaison avec quelques données. Tous les exemples de code sont simplifiés au maximum, il n'y aura pas de cas délicats avec des jetons d'authentification et des en-têtes ici. Uniquement POST et GET, GET et POST, et ainsi de suite environ 5 fois.
Alors allons-y.

1. Solution Java intégrée


Il serait étrange que la tâche ne puisse être résolue sans l'aide de bibliothèques tierces. Bien sûr que vous le pouvez! Mais triste. Le package java.net, à savoir HttpURLConnection, URL et URLEnconder.

Pour envoyer une demande GET, POST, vous devez créer un objet URL et ouvrir une connexion en fonction de celui-ci:

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

Ensuite, vous devez pimenter la connexion avec tous les paramètres:

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

Et obtenez un InputStream, d'où lire toutes les données reçues.

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

Et, en fait, nous obtenons ici une telle réponse (ce sera la même pour tous les exemples suivants, car nous travaillons avec les mêmes points de terminaison):

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

Dans le cas de la requête POST, tout est un peu plus compliqué. Nous voulons non seulement recevoir une réponse, mais aussi transmettre des données. Pour cela, nous devons les y mettre. La documentation nous indique que cela pourrait fonctionner comme suit:

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

Où getParamsString est une méthode simple qui mappe Map à String contenant des paires clé-valeur:

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

Une fois la création réussie, nous récupérons l'objet:

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

Une référence à la source que vous pouvez exécuter.

2. Apache HttpClient


Si nous nous éloignons des solutions intégrées, la première chose que nous rencontrons est le HttpClient d'Apache. Pour y accéder, nous avons besoin du fichier JAR lui-même. Ou, puisque j'utilise Maven, la dépendance correspondante est:

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

Et l'apparence des requêtes à l'aide de HttpClient est déjà bien meilleure ( source ):

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

Nous avons obtenu les mêmes données, mais nous avons écrit deux fois moins de code. Je me demande où d'autre peuvent-ils mener des recherches sur un problème aussi fondamental? Mais Apache a un autre module qui résout notre problème.

3. API Apache Fluent


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

Et déjà en utilisant Fluent Api, nos appels deviennent beaucoup plus lisibles ( source ):

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

Et en bonus - un exemple. Si nous voulons transférer des données vers le corps non pas comme un formulaire, mais comme le JSON préféré de tous:

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

En fait, les appels se sont effondrés en une seule ligne de code. Pour moi, c'est beaucoup plus convivial envers les développeurs que la toute première méthode.

4. Spring RestTemplate


Et ensuite? Une expérience supplémentaire m'a amené dans le monde du printemps. Et, sans surprise, le printemps dispose également d'outils pour résoudre notre tâche simple (étrange, non? Une tâche, même pas - un besoin! - à un niveau de base, mais pour une raison quelconque, il existe plus d'une solution). Et la première solution (basique) que vous trouverez dans l'écosystème Spring est RestTemplate. Et pour cela, nous devons tirer une partie considérable de tout le zoo. Donc, si vous devez envoyer une demande dans une application NONSpring, il est préférable de ne pas faire glisser toute la cuisine. Et s'il y a déjà un printemps, pourquoi pas? Comment tirer tout ce dont vous avez besoin pour cela, vous pouvez voir ici . Eh bien, en fait, la demande GET utilisant RestTemplate ressemble à ceci:

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

Capuche. MAIS! Je ne veux plus travailler avec une chaîne, d'autant plus qu'il y a une opportunité de recevoir non pas des chaînes, mais des objets prêts à l'emploi que l'on attend de recevoir! Créez un objet Post:

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

Ici:
Builder, Getter, Setter - le sucre de Lombok, ne pas tout écrire à la main. Oui, la voici, maman paresseuse.
JsonIgnoreProperties - de sorte qu'en cas de réception de champs inconnus, ne tombe pas en erreur, mais utilisez les champs que nous connaissons.
Eh bien, toString , pour sortir nos objets sur la console, et cela pourrait être lu. Eh bien, en fait, nos demandes GET et POST sont transformées en ( source ):

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

Et nous avons déjà des objets entre nos mains, et pas une chaîne dont nous avons besoin pour nous analyser.

Kul. Maintenant, nous pouvons écrire un wrapper autour de RestTemplate pour que la requête soit construite correctement. Ça n'a pas l'air si mal, mais, pour moi, ça peut encore être amélioré. Moins le code est écrit, moins il y a de risques d'erreur. Tout le monde sait que le problème principal est souvent PEBCAK (le problème existe entre le fauteuil et le clavier) ...

5. Spring Feign


Et voici la scène Feign, qui fait partie de Spring Cloud. Tout d'abord, ajoutez la dépendance Feign à l'environnement de printemps déjà ajouté précédemment:

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

En fait, il suffit de déclarer l'interface et de la pimenter avec une bonne petite annotation. Cette approche sera particulièrement intéressante pour ceux qui écrivent des contrôleurs à l'aide de Spring.

Voici ce que nous devons faire pour envoyer des demandes via Feign ( source ).

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

La beauté, non? Et oui, les modèles de données que nous avons écrits pour RestTemplate sont bien réutilisés ici.

6. Conclusion


Il n'y a pas d'autre moyen de mise en œuvre que les cinq présentés. Cette collection n'est que le reflet de l'expérience de l'auteur dans l'ordre dans lequel je les ai rencontrés et commencé à les utiliser dans des projets. Maintenant, j'utilise activement Feign, je profite de la vie et j'attends que quelque chose de plus pratique apparaisse pour que vous puissiez crier sur le moniteur "<nom de la bibliothèque>, je vous choisis!" - et tout était prêt à l'emploi et à l'intégration. En attendant, Feign.

PS Comme l'une des façons "paresseuses", vous pouvez considérer le client généré Swagger. Mais, comme on dit, il y a une nuance. Tous les développeurs n'utilisent pas Swagger pour documenter leurs API, et encore moins le font dans une qualité si élevée qu'ils peuvent facilement générer et utiliser le client, et ne pas obtenir une collection entomologique à la place, ce qui fera plus de mal que de bien.

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


All Articles