O protocolo WebSocket estabelece um canal de comunicação full-duplex sobre uma única conexão TCP persistente. Diferente do modelo tradicional HTTP, baseado em requisição e resposta (pull), o WebSocket permite que o servidor envie dados ativamente para o cliente (push), tornando-o ideal para sistemas de monitoramento em tempo real, dashboards financeiros e aplicações de mensagens.
Configuração de Dependências
Para iniciar a integração no ecossistema Spring Boot, é necessário incluir o starter de WebSocket no arquivo pom.xml do projeto:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Em seguida, é preciso registrar o ServerEndpointExporter. Este Bean é responsável por escanear as classes anotadas com @ServerEndpoint e registrá-las no container de WebSocket integrado.
@Configuration
public class WebSocketBrokerConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Desenvolvimento do Servidor de Mensagens
A classe de gerenciamento de conexões utiliza anotações padrão da especificação Java WebSocket (JSR 356). No exemplo abaixo, utilizamos um ConcurrentHashMap para gerenciar as sessões ativas de forma thread-safe, permitindo o envio de mensagens para usuários específicos via identificador único.
@Component
@ServerEndpoint("/ws/notifier/{clientId}")
public class RealTimeSocketHandler {
private static final Map<String, Session> clientSessions = new ConcurrentHashMap<>();
@OnOpen
public void onConnect(Session session, @PathParam("clientId") String clientId) {
clientSessions.put(clientId, session);
System.out.println("Conexão estabelecida: " + clientId);
}
@OnClose
public void onDisconnect(@PathParam("clientId") String clientId) {
clientSessions.remove(clientId);
System.out.println("Conexão encerrada: " + clientId);
}
@OnMessage
public void handleIncomingMessage(String payload, Session session) {
// Lógica para processar mensagens recebidas do frontend, se necessário
}
@OnError
public void handleError(Session session, Throwable error) {
error.printStackTrace();
}
public static void dispatchMessage(String clientId, String content) {
Session targetSession = clientSessions.get(clientId);
if (targetSession != null && targetSession.isOpen()) {
targetSession.getAsyncRemote().sendText(content);
}
}
}
Desafios de Injeção de Dependência
Um ponto crítico na implementação é que instâncias anotadas com @ServerEndpoint são gerenciadas pelo container WebSocket, e não diretamente pelo Spring Context. Isso significa que a injeção via @Autowired em campos de instância falhará (resultando em null). Para contornar isso, é comum utilizar o padrão Singleton ou acessar o contexto do Spring de forma estática para recuperar serviços necessários durante o ciclo de vida do socket.
Interface de Teste no Frontend
Para validar a comunicação, o cliente JavaScript pode se conectar ao endpoint utilizando a API nativa de WebSockets do navegador:
const socket = new WebSocket("ws://localhost:8080/ws/notifier/user_001");
socket.onopen = () => console.log("Conectado ao servidor");
socket.onmessage = (event) => {
const data = event.data;
console.log("Notificação recebida:", data);
};
socket.onclose = () => console.log("Conexão perdida");
Disparando Mensagens via API REST
É possível integrar o fluxo de dados de outras partes do sistema (como eventos de banco de dados ou mensagens MQTT) e repassá-los ao frontend através de um Controller:
@RestController
@RequestMapping("/api/notifications")
public class PushController {
@PostMapping("/send/{id}")
public ResponseEntity<String> triggerPush(@PathVariable String id, @RequestBody String message) {
RealTimeSocketHandler.dispatchMessage(id, message);
return ResponseEntity.ok("Mensagem enviada com sucesso");
}
}
Considerações de Produção
Embora a implementação básica seja funcional, ambientes de produção exigem cuidados adicionais:
- Escalabilidade Vertical: Como as sessões são mantidas em memória, o escalonamento horizontal exige um mecanismo de sincronização (como Redis Pub/Sub) para garantir que uma mensagem chegue ao cliente mesmo que ele esteja conectado a um nó diferente do emissor da mensagem.
- Keep-Alive: Implementar mecanismos de ping-pong para evitar que firewalls ou proxies encerrem conexões inativas por timeout.
- Segurança: Validar tokens de autenticação durante o handshake inicial do protocolo.