Viejas canciones sobre lo principal. Java y solicitudes salientes


( Ilustración )

Uno de los desafíos que enfrenta el 99.9% de los desarrolladores es acceder a puntos finales de terceros. Pueden ser tanto API externas como microservicios "propios". Ahora todos están atacando a los microservicios, sí. Recuperar o enviar datos es simple, pero a veces reinventan la rueda. ¿Puedes nombrar 5 formas de implementar consultas en Java (usando bibliotecas y sin ellas)? No, bienvenido al gato. ¿Eh? Ven y compara;)

0. Introducción


La tarea que resolveremos es extremadamente simple: necesitamos enviar una solicitud GET / POST y obtener una respuesta que viene en formato JSON. Para no escribir otro microservicio original, utilizaré un ejemplo que proporciona un conjunto de puntos finales con algunos datos. Todos los ejemplos de código se simplifican al máximo, aquí no habrá casos difíciles con tokens y encabezados de autenticación. Solo POST y GET, GET y POST, y así sucesivamente 5 veces más o menos.
Entonces vamos.

1. Solución Java incorporada


Sería extraño si la tarea no se pudiera resolver sin usar bibliotecas de terceros. Por supuesto que puedes! Pero triste El paquete java.net, a saber, HttpURLConnection, URL y URLEnconder.

Para enviar una solicitud que GET, esa POST, debe crear un objeto URL y abrir una conexión basada en él:

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

A continuación, necesita condimentar la conexión con todos los parámetros:

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

Y obtenga un InputStream, desde donde leer todos los datos recibidos.

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

Y, de hecho, aquí obtenemos dicha respuesta (será la misma para todos los ejemplos posteriores, porque trabajamos con los mismos puntos finales):

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

En el caso de la solicitud POST, todo es un poco más complicado. Queremos no solo recibir una respuesta, sino también transmitir datos. Para esto necesitamos ponerlos allí. La documentación nos dice que esto podría funcionar de la siguiente manera:

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

Donde getParamsString es un método simple que asigna Map to String que contiene pares clave-valor:

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

Tras una creación exitosa, recuperamos el objeto:

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

Una referencia a la fuente que puede ejecutar.

2. Apache HttpClient


Si nos alejamos de las soluciones integradas, lo primero que encontramos es el HttpClient de Apache. Para acceder, necesitamos el archivo JAR en sí. O, como estoy usando Maven, la dependencia correspondiente es:

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

Y el aspecto de las consultas con HttpClient ya es mucho mejor ( fuente ):

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

Obtuvimos los mismos datos, pero escribimos la mitad del código. Me pregunto dónde más pueden conducir búsquedas en un tema aparentemente tan básico. Pero Apache tiene otro módulo que resuelve nuestro problema.

3. API fluido de Apache


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

Y ya usando Fluent Api, nuestras llamadas se vuelven mucho más legibles ( fuente ):

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

Y como extra, un ejemplo. Si queremos transferir datos al cuerpo no como un formulario, sino como el JSON favorito de todos:

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

De hecho, las llamadas colapsaron en una sola línea de código. En cuanto a mí, esto es mucho más amigable con los desarrolladores que el primer método.

4. Spring RestTemplate


Que sigue Más experiencia me trajo al mundo de la primavera. Y, como era de esperar, la primavera también tiene herramientas para resolver nuestra tarea simple (extraño, ¿verdad? Una tarea, ni siquiera eso, ¡una necesidad! - en un nivel básico, pero por alguna razón hay más de una solución). Y la primera solución (básica) que encontrará en el ecosistema de Spring es RestTemplate. Y para esto necesitamos extraer una parte considerable de todo el zoológico. Entonces, si necesita enviar una solicitud en una aplicación NONSpring, entonces para esto es mejor no arrastrar toda la cocina. Y si ya hay una primavera, ¿por qué no? Cómo obtener todo lo que necesita para esto, puede ver aquí . Bueno, en realidad la solicitud GET con RestTemplate se ve así:

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

Campana PERO! ¡Ya no quiero trabajar con una cadena, más aún, así que existe la oportunidad de recibir no cadenas, sino objetos listos que esperamos recibir! Crear un objeto de publicación:

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

Aquí:
Constructor, Getter, Setter - azúcar de Lombok, no para escribir todo a mano. Sí, aquí está ella, flojera madre.
JsonIgnoreProperties : de modo que, en caso de recibir campos desconocidos, no se bloquee por error, sino que use los campos que conocemos.
Bueno, toString , para enviar nuestros objetos a la consola, y esto podría leerse. Bueno, en realidad nuestras solicitudes GET y POST se transforman en ( fuente ):

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

Y ya tenemos objetos en nuestras manos, y no una cadena que necesitamos analizar.

Kul Ahora podemos escribir un contenedor alrededor de RestTemplate para que la consulta se construya correctamente. No se ve tan mal, pero, en cuanto a mí, todavía se puede mejorar. Cuanto menos código se escriba, menos posibilidades de error. Todos saben que el problema principal es a menudo PEBCAK (el problema existe entre la silla y el teclado) ...

5. Fingir primavera


Y aquí viene el escenario Feign, que forma parte de Spring Cloud. Primero, agregue la dependencia de Feign al entorno de primavera que ya se agregó anteriormente:

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

De hecho, todo lo que se necesita es declarar la interfaz y enjuagarla con una pequeña y buena anotación. Especialmente este enfoque será atractivo para aquellos que escriben controladores usando Spring.

Esto es lo que debemos hacer para enviar solicitudes a través de Feign ( fuente ).

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

Belleza, ¿no es así? Y sí, los modelos de datos que escribimos para RestTemplate se reutilizan bien aquí.

6. Conclusión


No hay otra forma de implementación además de las cinco presentadas. Esta colección es solo un reflejo de la experiencia del autor en el orden en que los conocí y comencé a usarlos en proyectos. Ahora uso activamente Feign, disfruto de la vida y espero que aparezca algo más conveniente para que puedas gritar en el monitor "<nombre de biblioteca>, ¡te elijo a ti!" - Y todo estaba listo para su uso e integración. Mientras tanto, finge.

PD Como una de las formas "flojas" puede considerar el cliente generado Swagger. Pero, como dicen, hay un matiz. No todos los desarrolladores usan Swagger para documentar sus API, y menos aún lo hacen en una calidad tan alta que pueden generar y usar fácilmente el cliente, y no obtener una colección entomológica, lo que hará más daño que bien.

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


All Articles