Lorsque vous rĂ©solvez des tĂąches quotidiennes avec l'interface d'une application de bureau JavaFX, vous devez quand mĂȘme faire une demande au serveur Web. AprĂšs l'Ă©poque de J2EE et la terrible abrĂ©viation RMI, beaucoup de choses ont changĂ© et les appels au serveur sont devenus plus lĂ©gers. La norme pour les sockets Web et son Ă©change de messages texte simples de tout contenu convient Ă un tel problĂšme. Mais le problĂšme avec les applications d'entreprise est que la diversitĂ© et le nombre de demandes font de la crĂ©ation et du suivi des points de terminaison avec des services commerciaux sĂ©lectionnĂ©s sĂ©parĂ©ment une routine terrible et ajoute des lignes de code supplĂ©mentaires.
Mais que se passe-t-il si nous prenons une stratĂ©gie strictement typĂ©e avec RMI comme base, oĂč il y avait une interface java standard entre le client et le serveur qui dĂ©crit les mĂ©thodes, les arguments et les types de retour, oĂč quelques annotations ont Ă©tĂ© ajoutĂ©es, et le client n'a mĂȘme pas remarquĂ© comme par magie que l'appel Ă©tait passĂ© sur le rĂ©seau? Que se passe-t-il si ce n'est pas seulement du texte, mais que des objets java sĂ©rialisĂ©s sont transmis sur le rĂ©seau? Et si on ajoutait Ă cette stratĂ©gie la facilitĂ© des sockets web et leurs avantages de la possibilitĂ© d'appels push clients depuis le serveur? Que se passe-t-il si la rĂ©ponse asynchrone du socket Web pour le client est limitĂ©e Ă l'appel de blocage habituel, et pour un appel retardĂ©, ajoute la possibilitĂ© de renvoyer Future ou mĂȘme CompletableFuture ? Et si nous ajoutons la possibilitĂ© d'abonner le client Ă certains Ă©vĂ©nements du serveur? Que faire si le serveur a une session et une connexion Ă chaque client? Il peut s'avĂ©rer ĂȘtre un bon bundle transparent familier Ă tout programmeur java, car la magie sera cachĂ©e derriĂšre l'interface et lors des tests, les interfaces peuvent ĂȘtre facilement remplacĂ©es. Mais ce n'est pas tout pour les applications chargĂ©es qui traitent, par exemple, les cotations boursiĂšres.
Dans les applications d'entreprise de ma pratique, la vitesse d'exĂ©cution d'une requĂȘte SQL et de transfert de donnĂ©es sĂ©lectionnĂ©es Ă partir d'un SGBD est incommensurable avec les frais gĂ©nĂ©raux de sĂ©rialisation et d'appels rĂ©flĂ©chis. De plus, une terrible trace d'appels EJB, complĂ©tant le temps d'exĂ©cution Ă 4-10 ms mĂȘme pour la requĂȘte la plus simple, n'est pas un problĂšme, car la durĂ©e des requĂȘtes typiques est dans le couloir de 50ms Ă 250ms.
Commençons par le plus simple - nous utiliserons le modÚle d'objet Proxy pour implémenter la magie derriÚre les méthodes d'interface. Supposons que j'ai une méthode pour obtenir l'historique de la correspondance d'un utilisateur avec ses adversaires:
public interface ServerChat{ Map<String, <List<String>> getHistory(Date when, String login); }
Nous allons créer l'objet proxy à l'aide des outils java standard et appeler la méthode nécessaire dessus:
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; } }
Si en mĂȘme temps vous configurez des usines et implĂ©mentez l'instance d'objet proxy via l'interface via cdi-injection, vous obtiendrez la magie dans sa forme la plus pure. Dans le mĂȘme temps, l'ouverture / la fermeture d'une prise Ă chaque fois n'est pas du tout nĂ©cessaire. Au contraire, dans mes applications, le socket est constamment ouvert et prĂȘt Ă recevoir et traiter des messages. Maintenant, cela vaut la peine de voir ce qui se passe dans RMIoverWebSocketProxyHandler :
public class RMIoverWebSocketProxyHandler implements InvocationHandler { public static final int OVERHEAD = 0x10000; public static final int CLIENT_INPUT_BUFFER_SIZE = 0x1000000;
Et voici le client final EndPoint lui - mĂȘme :
@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()];
Ainsi, pour appeler n'importe quelle mĂ©thode de l'objet proxy, nous prenons une session de socket ouverte, envoyons les arguments passĂ©s et les dĂ©tails de la mĂ©thode qui doit ĂȘtre appelĂ©e sur le serveur, et attendons la rĂ©ponse avec le guide spĂ©cifiĂ© dans la demande Ă recevoir. DĂšs rĂ©ception de la rĂ©ponse, nous vĂ©rifions s'il y a une exception, et si tout va bien, nous mettons le rĂ©sultat de la rĂ©ponse dans Request et notifions le flux qui attend une rĂ©ponse dans WaitList. Je ne donnerai pas l'implĂ©mentation d'une telle WaitList, car elle est triviale. Le thread en attente, au mieux, continuera Ă fonctionner aprĂšs la ligne WaitList.putRequest (request, getRequestRunnable (request)); . AprĂšs le rĂ©veil, le thread vĂ©rifiera les exceptions dĂ©clarĂ©es dans la section des lancers et retournera le rĂ©sultat via return .
Les exemples de code ci-dessus sont des extraits de la bibliothĂšque, qui n'est pas encore prĂȘte Ă ĂȘtre publiĂ©e sur github. Il est nĂ©cessaire de rĂ©soudre les problĂšmes de licence. Il est logique de regarder l'implĂ©mentation du cĂŽtĂ© serveur dĂ©jĂ dans le code source lui-mĂȘme aprĂšs sa publication. Mais il n'y a rien de spĂ©cial lĂ -bas - une recherche est effectuĂ©e pour un objet ejb qui implĂ©mente l'interface spĂ©cifiĂ©e dans jndi via InitialContext et un appel rĂ©flĂ©chi est effectuĂ© en utilisant les dĂ©tails transmis. LĂ , bien sĂ»r, il y a encore beaucoup de choses intĂ©ressantes, mais un tel volume d'informations ne rentrera dans aucun article. Dans la bibliothĂšque elle-mĂȘme, le script d'appel de blocage ci-dessus a tout d'abord Ă©tĂ© implĂ©mentĂ©, car il est le plus simple. La prise en charge ultĂ©rieure des appels non bloquants via Future et CompletableFuture <> a Ă©tĂ© ajoutĂ©e. La bibliothĂšque est utilisĂ©e avec succĂšs dans tous les produits avec un client Java de bureau. Je serais heureux si quelqu'un partage son expĂ©rience de l'ouverture de code source liĂ© Ă gnu gpl 2.0 ( tyrus-standalone-client ).
Par consĂ©quent, il nâest pas difficile de crĂ©er une hiĂ©rarchie dâinvocation de mĂ©thodes Ă lâaide dâoutils IDE standard vers le formulaire dâinterface utilisateur lui-mĂȘme, sur lequel le gestionnaire de boutons tire des services distants. Dans le mĂȘme temps, nous obtenons un typage strict et une faible connectivitĂ© de la couche d'intĂ©gration client et serveur. La structure du code source de l'application est divisĂ©e en un client, un serveur et un noyau, qui est connectĂ© par dĂ©pendance au client et au serveur. C'est en elle que se trouvent toutes les interfaces distantes et objets transfĂ©rĂ©s. Et la routine du dĂ©veloppeur associĂ©e Ă la requĂȘte dans la base de donnĂ©es nĂ©cessite une nouvelle mĂ©thode dans l'interface et son implĂ©mentation cĂŽtĂ© serveur. Ă mon avis, beaucoup plus facile ...