
(
Ilustrasi )
Salah satu tantangan yang dihadapi oleh 99,9% pengembang adalah mengakses titik akhir pihak ketiga. Ini bisa berupa API eksternal dan layanan mikro "milik Anda". Sekarang semua orang memukul microservices, ya. Mengambil atau mengirim data adalah hal yang sederhana, tetapi terkadang mereka menemukan kembali kemudi. Bisakah Anda menyebutkan 5 cara untuk mengimplementasikan kueri di Jawa (menggunakan perpustakaan dan tanpa)? Tidak - selamat datang di kucing. Hah? Datang dan bandingkan;)
0. Pendahuluan
Tugas yang akan kita selesaikan sangat sederhana: kita perlu mengirim permintaan GET / POST dan mendapatkan respons yang datang dalam format JSON. Agar tidak menulis microservice asli lain, saya akan menggunakan
contoh yang menyediakan satu set titik akhir dengan beberapa data. Semua contoh kode disederhanakan secara maksimal, tidak akan ada kasus rumit dengan token dan header auth di sini. Hanya POST dan DAPATKAN, DAPATKAN dan POST, dan seterusnya 5 kali atau lebih.
Jadi ayo pergi.
1. Solusi Java bawaan
Akan aneh jika tugas itu tidak dapat diselesaikan tanpa menggunakan perpustakaan pihak ketiga. Tentu Anda bisa! Tapi sedih. Paket java.net, yaitu HttpURLConnection, URL dan URLEnconder.
Untuk mengirim permintaan yang MENDAPATKAN, POST itu, Anda perlu membuat objek URL dan membuka koneksi berdasarkan itu:
final URL url = new URL("http://jsonplaceholder.typicode.com/posts?_limit=10"); final HttpURLConnection con = (HttpURLConnection) url.openConnection();
Selanjutnya, Anda perlu membumbui koneksi dengan semua parameter:
con.setRequestMethod("GET"); con.setRequestProperty("Content-Type", "application/json"); con.setConnectTimeout(CONNECTION_TIMEOUT); con.setReadTimeout(CONNECTION_TIMEOUT);
Dan dapatkan InputStream, dari mana membaca semua data yang diterima.
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 ""; }
Dan, pada kenyataannya, di sini kita mendapatkan jawaban seperti itu (itu akan sama untuk semua contoh berikutnya, karena kami bekerja dengan titik akhir yang sama):
[ { "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" } ]
Dalam hal permintaan POST, semuanya sedikit lebih rumit. Kami ingin tidak hanya menerima jawaban, tetapi juga mengirimkan data. Untuk ini kita perlu meletakkannya di sana. Dokumentasi memberi tahu kami bahwa ini mungkin berfungsi sebagai berikut:
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();
Di mana getParamsString adalah metode sederhana yang memetakan Peta ke String yang berisi pasangan nilai kunci:
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; }
Setelah penciptaan berhasil, kami mendapatkan objek kembali:
{ "title": "foo", "body": "bar", "userId": "1", "id": 101}
Referensi ke sumber yang dapat Anda jalankan.
2. Apache HttpClient
Jika kita menjauh dari solusi bawaan, maka hal pertama yang kita temui adalah HttpClient dari Apache. Untuk akses, kita membutuhkan file JAR itu sendiri. Atau, karena saya menggunakan Maven, ketergantungan yang terkait adalah:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency>
Dan cara kueri terlihat menggunakan HttpClient sudah jauh lebih baik (
sumber ):
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();
Kami mendapat data yang sama, tetapi menulis kode sebanyak setengahnya. Saya ingin tahu di mana lagi mereka dapat mengarahkan pencarian dalam masalah yang tampaknya mendasar ini? Tetapi Apache memiliki modul lain yang memecahkan masalah kami.
3. Apache Fluent API
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.6</version> </dependency>
Dan sudah menggunakan Fluent Api, panggilan kami menjadi jauh lebih mudah dibaca (
sumber ):
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());
Dan sebagai bonus - contoh. Jika kami ingin mentransfer data ke tubuh bukan sebagai formulir, tetapi sebagai JSON favorit semua orang:
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());
Faktanya, panggilan itu diciutkan menjadi satu baris kode. Bagi saya, ini jauh lebih ramah terhadap pengembang daripada metode pertama.
4. Spring RestTemplate
Apa selanjutnya Pengalaman lebih lanjut membawa saya ke dunia Musim Semi. Dan, tidak mengherankan, pegas juga memiliki alat untuk menyelesaikan tugas sederhana kami (aneh, kan? Tugas, bahkan bukan itu - kebutuhan! - pada tingkat dasar, tetapi untuk beberapa alasan ada lebih dari satu solusi). Dan solusi pertama (dasar) yang akan Anda temukan di ekosistem Spring adalah RestTemplate. Dan untuk ini kita perlu menarik sebagian besar dari seluruh kebun binatang. Jadi jika Anda perlu mengirim permintaan dalam aplikasi NONSpring, maka untuk ini lebih baik tidak menyeret seluruh dapur. Dan jika sudah ada pegas, mengapa tidak? Cara menarik semua yang Anda butuhkan untuk ini, Anda bisa lihat di
sini . Ya, sebenarnya permintaan GET menggunakan RestTemplate terlihat seperti ini:
final RestTemplate restTemplate = new RestTemplate(); final String stringPosts = restTemplate.getForObject("http://jsonplaceholder.typicode.com/posts?_limit=10", String.class); System.out.println(stringPosts);
Kap mesin TAPI! Saya tidak ingin bekerja dengan string lagi, terlebih lagi ada kesempatan untuk menerima bukan string, tetapi benda siap pakai yang kami harapkan akan terima! Buat objek Posting:
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); } }
Di sini:
Builder, Getter, Setter - gula dari Lombok, tidak menulis semuanya dengan tangan. Ya, ini dia, ibu malas.
JsonIgnoreProperties - sehingga dalam hal menerima bidang yang tidak dikenal, jangan menabrak kesalahan, tetapi gunakan bidang yang kita tahu.
Nah,
toString , untuk menampilkan objek kita ke konsol, dan ini bisa dibaca. Sebenarnya, permintaan GET dan POST kami diubah menjadi (
sumber ):
Dan kita sudah memiliki benda di tangan kita, dan bukan seutas tali yang perlu kita uraikan sendiri.
Kul. Sekarang kita dapat menulis beberapa pembungkus di sekitar RestTemplate sehingga kueri dibangun dengan benar. Kelihatannya tidak terlalu buruk, tetapi bagi saya itu masih bisa diperbaiki. Semakin sedikit kode yang ditulis, semakin kecil kemungkinan kesalahan. Semua orang tahu bahwa masalah utama sering PEBCAK (Masalah Ada di antara Kursi dan Keyboard) ...
5. Musim Semi Feign
Dan inilah panggung Feign, yang merupakan bagian dari Spring Cloud. Pertama, tambahkan ketergantungan Feign ke lingkungan pegas yang sudah ditambahkan sebelumnya:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.5.RELEASE</version> </dependency>
Faktanya, yang dibutuhkan hanyalah mendeklarasikan antarmuka dan membumbui dengan sedikit anotasi. Terutama pendekatan ini akan menarik bagi mereka yang menulis pengendali menggunakan pegas.
Inilah yang perlu kita lakukan untuk mengirim permintaan melalui Feign (
sumber ).
@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); }
Kecantikan, bukan? Dan ya, model data yang kami tulis untuk RestTemplate digunakan kembali dengan baik di sini.
6. Kesimpulan
Tidak ada satu cara lain implementasi selain lima yang disajikan. Koleksi ini hanya refleksi dari pengalaman penulis dalam urutan di mana saya bertemu dan mulai menggunakannya dalam proyek. Sekarang saya secara aktif menggunakan Feign, menikmati hidup dan menunggu sesuatu yang lebih nyaman untuk ditampilkan sehingga Anda dapat berteriak ke monitor "<nama perpustakaan>, saya memilih Anda!" - dan semuanya siap digunakan dan diintegrasikan. Sementara itu, Feign.
PS Sebagai salah satu cara "malas" Anda dapat mempertimbangkan Swagger klien yang dihasilkan. Tapi, seperti kata mereka, ada nuansa. Tidak semua pengembang menggunakan Swagger untuk mendokumentasikan API mereka, dan bahkan lebih sedikit melakukannya dengan kualitas tinggi sehingga mereka dapat dengan mudah menghasilkan dan menggunakan klien, dan bukannya mendapatkan koleksi entomologis, yang akan lebih berbahaya daripada kebaikan.