
(
Ilustração )
Um dos desafios enfrentados por 99,9% dos desenvolvedores é acessar pontos de extremidade de terceiros. Pode ser APIs externas e microsserviços “próprios”. Agora todo mundo está atingindo microsserviços, sim. Recuperar ou enviar dados é simples, mas às vezes eles reinventam a roda. Você pode citar 5 maneiras de implementar consultas em Java (usando e sem bibliotecas)? Não - bem-vindo ao gato. Hein? Venha e compare;)
0. Introdução
A tarefa que resolveremos é extremamente simples: precisamos enviar uma solicitação GET / POST e obter uma resposta que vem no formato JSON. Para não gravar outro microsserviço original, usarei
um exemplo que fornece um conjunto de terminais com alguns dados. Todos os exemplos de código são simplificados ao máximo, não haverá casos complicados com tokens de autenticação e cabeçalhos aqui. Somente POST e GET, GET e POST e assim por diante 5 vezes.
Então vamos lá.
1. Solução Java integrada
Seria estranho se a tarefa não pudesse ser resolvida sem o uso de bibliotecas de terceiros. Claro que você pode! Mas triste. O pacote java.net, chamado HttpURLConnection, URL e URLEnconder.
Para enviar uma solicitação que GET, que POST, você precisa criar um objeto de URL e abrir uma conexão com base nele:
final URL url = new URL("http://jsonplaceholder.typicode.com/posts?_limit=10"); final HttpURLConnection con = (HttpURLConnection) url.openConnection();
Em seguida, você precisa apimentar a conexão com todos os parâmetros:
con.setRequestMethod("GET"); con.setRequestProperty("Content-Type", "application/json"); con.setConnectTimeout(CONNECTION_TIMEOUT); con.setReadTimeout(CONNECTION_TIMEOUT);
E obtenha um InputStream, de onde ler todos os dados recebidos.
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 ""; }
E, de fato, aqui obtemos uma resposta (será a mesma para todos os exemplos subsequentes, porque trabalhamos com os mesmos pontos de extremidade):
[ { "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" } ]
No caso da solicitação POST, tudo é um pouco mais complicado. Queremos não apenas receber uma resposta, mas também transmitir dados. Para isso, precisamos colocá-los lá. A documentação nos diz que isso pode funcionar da seguinte maneira:
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();
Onde getParamsString é um método simples que mapeia Mapear para String contendo pares de valores-chave:
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; }
Após a criação bem-sucedida, recuperamos o objeto:
{ "title": "foo", "body": "bar", "userId": "1", "id": 101}
Uma referência à fonte que você pode executar.
2. Apache HttpClient
Se nos afastarmos das soluções integradas, a primeira coisa que encontramos é o HttpClient do Apache. Para acessar, precisamos do próprio arquivo JAR. Ou, como estou usando o Maven, a dependência correspondente é:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency>
E a aparência das consultas usando o HttpClient já é muito melhor (
fonte ):
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();
Obtivemos os mesmos dados, mas escrevemos metade do código. Gostaria de saber onde mais eles podem levar pesquisas em uma questão aparentemente básica? Mas o Apache possui outro módulo que resolve nosso problema.
3. API Apache Fluent
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.6</version> </dependency>
E já usando o Fluent Api, nossas chamadas se tornam muito mais legíveis (
fonte ):
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());
E como um bônus - um exemplo. Se queremos transferir dados para o corpo não como um formulário, mas como o 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 fato, as chamadas entraram em colapso em uma linha de código. Quanto a mim, isso é muito mais amigável para os desenvolvedores do que o primeiro método.
4. Modelo Rest Rest
O que vem depois? Outras experiências me trouxeram ao mundo da primavera. E, não surpreendentemente, a primavera também possui ferramentas para resolver nossa tarefa simples (estranha, certo? Uma tarefa, nem mesmo essa - uma necessidade! - em um nível básico, mas por alguma razão, há mais de uma solução). E a primeira solução (básica) que você encontrará no ecossistema Spring é RestTemplate. E para isso, precisamos puxar uma parte considerável de todo o zoológico. Portanto, se você precisar enviar uma solicitação em um aplicativo NONSpring, é melhor não arrastar a cozinha inteira. E se já existe uma primavera, por que não? Como puxar tudo o que você precisa para isso, você pode ver
aqui . Bem, na verdade a solicitação GET usando RestTemplate é semelhante a esta:
final RestTemplate restTemplate = new RestTemplate(); final String stringPosts = restTemplate.getForObject("http://jsonplaceholder.typicode.com/posts?_limit=10", String.class); System.out.println(stringPosts);
Hood. MAS! Não quero mais trabalhar com uma string, tanto mais que há uma oportunidade de receber não strings, mas objetos prontos que esperamos receber! Crie um objeto 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); } }
Aqui:
Builder, Getter, Setter - açúcar da Lombok, para não escrever tudo à mão. Sim, aqui está ela, mãe preguiçosa.
JsonIgnoreProperties - para que, no caso de receber campos desconhecidos, não colidir com erro, mas use os campos que conhecemos.
Bem,
toString , para enviar nossos objetos para o console, e isso pode ser lido. Bem, na verdade, nossas solicitações GET e POST são transformadas em (
fonte ):
E já temos objetos em nossas mãos, e não uma corda que precisamos analisar.
Kul. Agora podemos escrever algum wrapper em torno do RestTemplate para que a consulta seja criada corretamente. Não parece tão ruim, mas, quanto a mim, ainda pode ser melhorado. Quanto menos código for escrito, menor a chance de erro. Todo mundo sabe que o principal problema geralmente é o PEBCAK (o problema existe entre a cadeira e o teclado) ...
5. Falsificação da Primavera
E aqui vem o estágio Feign, que faz parte do Spring Cloud. Primeiro, adicione a dependência Feign ao ambiente de primavera já adicionado anteriormente:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.5.RELEASE</version> </dependency>
De fato, tudo o que é necessário é declarar a interface e incrementá-la com uma boa pequena anotação. Especialmente, essa abordagem será atraente para quem escreve controladores usando a mola.
Aqui está o que precisamos fazer para enviar solicitações através do Feign (
fonte ).
@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); }
Beleza, não é? E sim, os modelos de dados que escrevemos para o RestTemplate são bem reutilizados aqui.
6. Conclusão
Não há outra maneira de implementação além das cinco apresentadas. Esta coleção é apenas um reflexo da experiência do autor na ordem em que os conheci e comecei a usá-los em projetos. Agora eu uso ativamente o Feign, aproveito a vida e espero que algo mais conveniente apareça para que você possa gritar no monitor "<nome da biblioteca>, eu escolhi você!" - e tudo estava pronto para uso e integração. Enquanto isso, Feign.
PS Como uma das maneiras "preguiçosas", você pode considerar o cliente Swagger gerado. Mas, como se costuma dizer, há uma nuance. Nem todos os desenvolvedores usam o Swagger para documentar suas APIs, e menos ainda o fazem com uma qualidade tão alta que eles podem facilmente gerar e usar o cliente, e não obter uma coleção entomológica, o que fará mais mal do que bem.