Ao resolver tarefas diárias com a interface de um aplicativo de desktop baseado em JavaFX, você deve fazer uma solicitação ao servidor da Web de qualquer maneira. Após os dias do J2EE e a terrível abreviatura RMI, muita coisa mudou e as chamadas do servidor ficaram mais leves. O padrão para soquetes da Web e sua troca de mensagens de texto simples de qualquer conteúdo é adequado para esse problema. Mas o problema com aplicativos corporativos é que a diversidade e o número de solicitações tornam a criação e o rastreamento de EndPoints com serviços de negócios selecionados separadamente uma péssima rotina e adicionam linhas de código extras.
Mas e se adotássemos uma estratégia estritamente digitada com o RMI como base, onde houvesse uma interface java padrão entre o cliente e o servidor que descrevesse métodos, argumentos e tipos de retorno, onde algumas anotações foram adicionadas e o cliente nem percebeu magicamente que a chamada estava sendo feita pela rede? E se não apenas texto, mas objetos java serializados são transmitidos pela rede? E se adicionarmos a essa estratégia a facilidade dos soquetes da Web e suas vantagens da possibilidade de chamadas do cliente pelo servidor? E se a resposta assíncrona do soquete da web para o cliente for restringida na chamada de bloqueio usual e, para uma chamada atrasada, adicionar a possibilidade de retornar Future ou mesmo CompletableFuture ? E se adicionarmos a capacidade de inscrever o cliente em certos eventos do servidor? E se o servidor tiver uma sessão e conexão com cada cliente? Pode ser um bom pacote transparente familiar a qualquer programador java, pois a mágica fica oculta por trás da interface e, no teste, as interfaces podem ser facilmente substituídas. Mas isso não é tudo para aplicativos carregados que processam, por exemplo, cotações do mercado de ações.
Em aplicativos corporativos da minha prática, a velocidade de executar uma consulta sql e transferir dados selecionados de um DBMS é incomensurável com a sobrecarga de serialização e chamadas reflexivas. Além disso, um rastreamento terrível de chamadas EJB, complementando o tempo de execução de 4 a 10 ms, mesmo para a solicitação mais simples, não é um problema, já que a duração das solicitações típicas ocorre no corredor de 50ms a 250ms.
Vamos começar com o mais simples - usaremos o padrão de objeto Proxy para implementar a mágica por trás dos métodos de interface. Suponha que eu tenha um método para obter o histórico da correspondência de um usuário com seus oponentes:
public interface ServerChat{ Map<String, <List<String>> getHistory(Date when, String login); }
Criaremos o objeto proxy usando ferramentas java padrão e chamaremos o método necessário:
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; } }
Se, ao mesmo tempo, você configurar fábricas e implementar a instância do objeto proxy através da interface por meio da injeção de cdi, você obterá a mágica em sua forma mais pura. Ao mesmo tempo, abrir / fechar um soquete toda vez não é necessário. Pelo contrário, em meus aplicativos, o soquete está constantemente aberto e pronto para receber e processar mensagens. Agora vale a pena ver o que acontece no RMIoverWebSocketProxyHandler :
public class RMIoverWebSocketProxyHandler implements InvocationHandler { public static final int OVERHEAD = 0x10000; public static final int CLIENT_INPUT_BUFFER_SIZE = 0x1000000;
E aqui está o próprio cliente EndPoint em si :
@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()];
Portanto, para chamar qualquer método do objeto proxy, fazemos uma sessão de soquete aberto, enviamos os argumentos e detalhes passados do método que deve ser chamado no servidor e aguardamos a resposta com o guia especificado na solicitação. Após o recebimento da resposta, verificamos uma exceção e, se estiver tudo bem, colocamos o resultado da resposta em Request e notificamos o fluxo que está aguardando uma resposta em WaitList. Não darei a implementação de tal WaitList, pois é trivial. O encadeamento em espera, na melhor das hipóteses, continuará a funcionar após a linha WaitList.putRequest (request, getRequestRunnable (request)); . Depois de acordar, o encadeamento verificará as exceções declaradas na seção throws e retornará o resultado via return .
Os exemplos de código acima são trechos da biblioteca, que ainda não estão prontos para publicação no github. É necessário resolver problemas de licenciamento. Faz sentido olhar para a implementação do lado do servidor já no próprio código-fonte após sua publicação. Mas não há nada de especial lá - é feita uma pesquisa por um objeto ejb que implementa a interface especificada no jndi via InitialContext e uma chamada reflexiva é feita usando os detalhes transmitidos. É claro que ainda há muitas coisas interessantes, mas esse volume de informações não cabe em nenhum artigo. Na própria biblioteca, o script de chamada de bloqueio acima foi implementado antes de tudo, pois é o mais simples. Mais tarde, foi adicionado suporte para chamadas sem bloqueio via Future e CompletableFuture <> . A biblioteca é usada com sucesso em todos os produtos com um cliente java de desktop. Eu ficaria feliz se alguém compartilhar sua experiência de abertura de código-fonte que está vinculado ao gnu gpl 2.0 ( tyrus-standalone-client ).
Como resultado, não é difícil criar uma hierarquia de invocação de método usando as ferramentas padrão do IDE até o próprio formulário da interface do usuário, no qual o manipulador de botão extrai serviços remotos. Ao mesmo tempo, temos uma digitação estrita e fraca conectividade da camada de integração de cliente e servidor. A estrutura do código-fonte do aplicativo é dividida em um cliente, um servidor e um kernel, que é conectado por dependência ao cliente e ao servidor. É nele que todas as interfaces remotas e objetos transferidos estão localizados. E a rotina do desenvolvedor associada à consulta no banco de dados requer um novo método na interface e sua implementação no lado do servidor. Na minha opinião, muito mais fácil ...