عند حل المهام اليومية باستخدام واجهة تطبيق سطح المكتب المستند إلى JavaFX ، يجب عليك تقديم طلب إلى خادم الويب على أي حال. بعد أيام J2EE والاختصار الرهيب RMI ، تغير الكثير ، وأصبحت مكالمات الخادم أكثر خفة. إن معيار مآخذ الويب وتبادل الرسائل النصية البسيطة لأي محتوى مناسب لمثل هذه المشكلة. لكن المشكلة في تطبيقات المؤسسة هي أن التنوع وعدد الطلبات يجعل إنشاء نقاط النهاية وتتبعها مع خدمات الأعمال المحددة بشكل منفصل روتينًا رهيبًا ويضيف أسطرًا إضافية من التعليمات البرمجية.
ولكن ماذا لو أخذنا استراتيجية مكتوبة بدقة مع RMI كأساس ، حيث كانت هناك واجهة جافا قياسية بين العميل والخادم تصف الأساليب والحجج وأنواع الإرجاع ، حيث تمت إضافة زوج من التعليقات التوضيحية ، ولم يلاحظ العميل بشكل سحري أن المكالمة تجري عبر الشبكة؟ ماذا لو لم يكن النص فقط ، ولكن تم نقل كائنات جافا المتسلسلة عبر الشبكة؟ ماذا لو أضفنا سهولة مآخذ الويب ومزاياها إلى إمكانية دفع مكالمات العميل من الخادم إلى هذه الإستراتيجية؟ ماذا لو تم تقييد الاستجابة غير المتزامنة لمقبس الويب للعميل في مكالمة الحظر المعتادة ، وللمكالمة المتأخرة ، أضف إمكانية إرجاع Future أو حتى CompletableFuture ؟ ماذا لو أضفنا إمكانية اشتراك العميل في أحداث معينة من الخادم؟ ماذا لو كان الخادم لديه جلسة واتصال بكل عميل؟ قد تتحول إلى حزمة شفافة جيدة مألوفة لأي مبرمج جافا ، حيث سيتم إخفاء السحر خلف الواجهة ، وفي الاختبار يمكن استبدال الواجهات بسهولة. ولكن هذا ليس فقط للتطبيقات المحملة التي تعالج ، على سبيل المثال ، أسعار سوق الأسهم.
في تطبيقات الشركات من ممارستي ، فإن سرعة تنفيذ استعلام SQL ونقل البيانات المحددة من نظام إدارة قواعد البيانات (DBMS) لا تتناسب مع النفقات العامة للتسلسل والمكالمات العاكسة. علاوة على ذلك ، لا يعد التتبع الرهيب لمكالمات EJB ، المكمل لوقت التنفيذ إلى 4-10 مللي ثانية حتى لأبسط طلب ، مشكلة ، لأن مدة الطلبات النموذجية في الممر من 50 مللي ثانية إلى 250 مللي ثانية.
لنبدأ بالأبسط - سنستخدم نمط كائن الوكيل لتنفيذ السحر وراء طرق الواجهة. افترض أن لدي طريقة للحصول على تاريخ مراسلات المستخدم مع خصومه:
public interface ServerChat{ Map<String, <List<String>> getHistory(Date when, String login); }
سنقوم بإنشاء كائن الوكيل باستخدام أدوات جافا القياسية واستدعاء الطريقة اللازمة عليه:
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; } }
إذا قمت في نفس الوقت بإعداد المصانع ، وقمت بتنفيذ مثيل كائن الوكيل عبر الواجهة من خلال حقن cdi ، فستحصل على السحر في أنقى صوره. في نفس الوقت ، فتح / إغلاق مقبس في كل مرة ليس ضروريًا على الإطلاق. على العكس من ذلك ، في المقبس الخاص بي مفتوح باستمرار وجاهز لاستقبال ومعالجة الرسائل. من الجدير الآن رؤية ما يحدث في RMIoverWebSocketProxyHandler :
public class RMIoverWebSocketProxyHandler implements InvocationHandler { public static final int OVERHEAD = 0x10000; public static final int CLIENT_INPUT_BUFFER_SIZE = 0x1000000;
وهنا العميل الفعلي EndPoint نفسه :
@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()];
وبالتالي ، لاستدعاء أي طريقة لكائن الوكيل ، نأخذ جلسة مقبس مفتوحة ، ونرسل الوسيطات التي تم تمريرها وتفاصيل الطريقة التي يجب استدعاءها على الخادم ، وننتظر الرد مع الدليل المحدد في الطلب ليتم استلامه. عند استلام الاستجابة ، نتحقق من وجود استثناء ، وإذا كان كل شيء على ما يرام ، فإننا نضع نتيجة الاستجابة في الطلب ونبلغ الدفق الذي ينتظر استجابة في WaitList. لن أعطي تطبيق WaitList ، لأنه تافه. مؤشر ترابط الانتظار ، في أحسن الأحوال ، سيستمر في العمل بعد سطر WaitList.putRequest (طلب ، getRequestRunnable (طلب)) ؛ . بعد الاستيقاظ ، سيتحقق مؤشر الترابط من الاستثناءات الموضحة في قسم الرميات ، وسيعيد النتيجة عبر الإرجاع .
أمثلة الكود أعلاه هي مقتطفات من المكتبة ، وهي ليست جاهزة بعد للنشر على github. من الضروري حل مشاكل الترخيص. من المنطقي أن ننظر إلى تنفيذ جانب الخادم بالفعل في شفرة المصدر نفسها بعد نشرها. ولكن لا يوجد شيء خاص هناك - يتم البحث عن كائن ejb الذي يقوم بتنفيذ الواجهة المحددة في jndi عبر InitialContext ويتم إجراء مكالمة عاكسة باستخدام التفاصيل المرسلة. هناك ، بالطبع ، لا يزال هناك الكثير من الأشياء المثيرة للاهتمام ، ولكن هذا الحجم من المعلومات لن يتناسب مع أي مقال. في المكتبة نفسها ، تم تنفيذ البرنامج النصي المكالمة أعلاه أعلاه أولاً ، لأنه أبسط. تمت إضافة دعم لاحق للمكالمات غير المحظورة عبر Future و CompletableFuture <> . يتم استخدام المكتبة بنجاح في جميع المنتجات مع عميل جافا لسطح المكتب. سأكون سعيدًا إذا شارك شخص ما تجربته في فتح شفرة المصدر المرتبطة بـ gnu gpl 2.0 ( tyrus-standalone-client ).
ونتيجة لذلك ، ليس من الصعب إنشاء تسلسل هرمي لاستدعاء الطريقة باستخدام أدوات IDE القياسية حتى نموذج واجهة المستخدم نفسه ، حيث يسحب معالج الأزرار الخدمات البعيدة. في الوقت نفسه ، نحصل على كتابة صارمة واتصال ضعيف لطبقة تكامل العميل والخادم. تنقسم بنية شفرة مصدر التطبيق إلى عميل وخادم ونواة ، والتي يتم توصيلها عن طريق الإدمان لكل من العميل والخادم. يوجد فيه جميع الواجهات البعيدة والأشياء المنقولة. ويتطلب روتين المطور المرتبط بالاستعلام في قاعدة البيانات طريقة جديدة في الواجهة وتنفيذها من جانب الخادم. في رأيي ، أسهل بكثير ...