Saat menyelesaikan tugas sehari-hari dengan antarmuka aplikasi desktop berbasis JavaFX, Anda harus tetap membuat permintaan ke server web. Setelah masa J2EE dan singkatan RMI yang mengerikan, banyak yang telah berubah, dan panggilan server menjadi lebih ringan. Standar untuk soket web dan pertukaran pesan teks sederhana dari konten apa pun cocok untuk masalah seperti itu. Tetapi masalah dengan aplikasi perusahaan adalah bahwa keragaman dan jumlah permintaan membuat membuat dan melacak EndPoints dengan layanan bisnis yang dipilih secara terpisah menjadi rutinitas yang mengerikan dan menambahkan baris kode tambahan.
Tetapi bagaimana jika kita mengambil strategi yang diketik ketat dengan RMI sebagai dasar, di mana ada antarmuka java standar antara klien dan server yang menggambarkan metode, argumen dan jenis pengembalian, di mana beberapa anotasi ditambahkan, dan klien bahkan tidak secara ajaib melihat bahwa panggilan itu dilakukan melalui jaringan? Bagaimana jika bukan hanya teks, tetapi objek java serial ditransmisikan melalui jaringan? Bagaimana jika kita menambahkan kemudahan soket web dan keuntungannya pada kemungkinan panggilan klien dari server ke strategi ini? Bagaimana jika respons asinkron dari soket web untuk klien diekang ke panggilan pemblokiran yang biasa, dan untuk panggilan yang tertunda, tambahkan kemungkinan untuk mengembalikan Masa Depan atau bahkan CompletableFuture ? Bagaimana jika kita menambahkan kemampuan untuk berlangganan klien ke acara-acara tertentu dari server? Bagaimana jika server memiliki sesi dan koneksi ke setiap klien? Mungkin berubah menjadi bundel transparan yang baik yang akrab bagi setiap programmer java, karena sihir akan disembunyikan di balik antarmuka, dan dalam pengujian antarmuka dapat dengan mudah diganti. Tapi itu tidak semua untuk aplikasi dimuat yang memproses, misalnya, kutipan pasar saham.
Dalam aplikasi perusahaan dari praktik saya, kecepatan mengeksekusi kueri sql dan mentransfer data yang dipilih dari DBMS tidak dapat dibandingkan dengan overhead serialisasi dan panggilan reflektif. Selain itu, jejak panggilan EJB yang mengerikan, menambah waktu eksekusi menjadi 4-10 ms bahkan untuk permintaan yang paling sederhana, bukan masalah, karena durasi permintaan tipikal adalah di koridor dari 50 ms hingga 250 ms.
Mari kita mulai dengan yang paling sederhana - kita akan menggunakan pola objek Proxy untuk menerapkan keajaiban di balik metode antarmuka. Misalkan saya memiliki metode untuk mendapatkan riwayat korespondensi pengguna dengan lawan-lawannya:
public interface ServerChat{ Map<String, <List<String>> getHistory(Date when, String login); }
Kami akan membuat objek proxy menggunakan alat java standar dan memanggil metode yang diperlukan di dalamnya:
public class ClientProxyUtils { public static BiFunction<String, Class, RMIoverWebSocketProxyHandler> defaultFactory = RMIoverWebSocketProxyHandler::new; public static <T> T create(Class<T> clazz, String jndiName) { T f = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, defaultFactory.apply(jndiName, clazz)); return f; } }
Jika pada saat yang sama Anda mengatur pabrik, dan mengimplementasikan instance objek proxy melalui antarmuka melalui cdi-injection, Anda akan mendapatkan keajaiban dalam bentuk paling murni. Pada saat yang sama, membuka / menutup soket setiap saat tidak diperlukan sama sekali. Sebaliknya, dalam aplikasi saya soket selalu terbuka dan siap untuk menerima dan memproses pesan. Sekarang layak melihat apa yang terjadi di RMIoverWebSocketProxyHandler :
public class RMIoverWebSocketProxyHandler implements InvocationHandler { public static final int OVERHEAD = 0x10000; public static final int CLIENT_INPUT_BUFFER_SIZE = 0x1000000;
Dan inilah EndPoint klien yang sebenarnya itu sendiri :
@ClientEndpoint public class ClientRMIHandler { public static volatile Session clientSession; @OnOpen public void onOpen(Session session) { clientSession = session; } @OnMessage public void onMessage(ByteBuffer message, Session session) { try { final Object readInput = read(message); if (readInput instanceof Response) { standartResponse((Response) readInput); } } catch (IOException ex) { WaitList.clean(); notifyErrorListeners(new RuntimeException(FATAL_ERROR_MESSAGE, ex)); } } private void standartResponse(final Response response) throws RuntimeException { if (response.guid == null) { if (response.error != null) { notifyErrorListeners(response.error); return; } WaitList.clean(); final RuntimeException runtimeException = new RuntimeException(FATAL_ERROR_MESSAGE); notifyErrorListeners(runtimeException); throw runtimeException; } else { WaitList.processResponse(response); } } @OnClose public void onClose(Session session, CloseReason closeReason) { WaitList.clean(); } @OnError public void onError(Session session, Throwable error) { notifyErrorListeners(error); } private static Object read(ByteBuffer message) throws ClassNotFoundException, IOException { Object readObject; byte[] b = new byte[message.remaining()];
Dengan demikian, untuk memanggil metode apa pun dari objek proxy, kami mengambil sesi soket terbuka, mengirim argumen yang dikirimkan dan rincian metode yang harus dipanggil di server, dan menunggu respons dengan panduan yang ditentukan dalam permintaan yang akan diterima. Setelah menerima respons, kami memeriksa pengecualian, dan jika semuanya baik-baik saja, kami memasukkan hasil respons dalam Permintaan dan memberi tahu aliran yang menunggu respons di WaitList. Saya tidak akan memberikan implementasi WaitList tersebut, karena sepele. Utas tunggu, paling-paling, akan terus berfungsi setelah baris WaitList.putRequest (request, getRequestRunnable (request)); . Setelah bangun, utas akan memeriksa pengecualian yang dinyatakan di bagian lemparan , dan akan mengembalikan hasilnya melalui pengembalian .
Contoh kode di atas adalah kutipan dari perpustakaan, yang belum siap untuk dikirim ke github. Hal ini diperlukan untuk mengatasi masalah perizinan. Masuk akal untuk melihat implementasi dari sisi server sudah dalam kode sumber itu sendiri setelah publikasi. Tapi tidak ada yang istimewa di sana - pencarian dilakukan untuk objek ejb yang mengimplementasikan antarmuka yang ditentukan dalam jndi melalui InitialContext dan panggilan reflektif dibuat menggunakan rincian yang dikirim. Tentu saja masih ada banyak hal menarik, tetapi volume informasi seperti itu tidak akan cocok dengan artikel mana pun. Di perpustakaan itu sendiri, skrip panggilan pemblokiran di atas diimplementasikan pertama-tama, karena itu adalah yang paling sederhana. Dukungan kemudian untuk panggilan non-blocking melalui Future dan CompletableFuture <> telah ditambahkan. Perpustakaan berhasil digunakan di semua produk dengan klien java desktop. Saya akan senang jika seseorang berbagi pengalaman mereka membuka kode sumber yang terhubung dengan gnu gpl 2.0 ( tyrus-standalone-client ).
Akibatnya, tidak sulit untuk membangun hierarki pemanggilan metode menggunakan alat IDE standar ke bentuk UI itu sendiri, di mana penangan tombol menarik layanan jarak jauh. Pada saat yang sama, kami mendapatkan ketikan yang ketat dan konektivitas yang lemah dari lapisan integrasi klien dan server. Struktur kode sumber aplikasi dibagi menjadi klien, server, dan kernel, yang dihubungkan oleh kecanduan klien dan server. Di dalamnya semua antarmuka jarak jauh dan objek yang ditransfer berada. Dan rutin pengembang yang terkait dengan permintaan dalam database memerlukan metode baru dalam antarmuka dan implementasinya di sisi server. Menurut saya, lebih mudah ...