- build.gradle dependencies 세션에 websocket을 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-websocket'
- @SpringBootApplication 어노테이션이 정의된 클래스에 아래 빈정의를 추가
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
- 웹소켓 EndPoint 서비스 클래스 생성한다.
@ServerEndPoint어노테이션의 value로 정의한 url(ws://localhost:8080/notify)로 접속하여 테스트!
package com.websocket.websocket;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.java.Log;
@Log
@Component
@ServerEndpoint(value="/notify")
public class NotifyWS {
private static int onlineCount = 0;
public static Set<Session> subscribers = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session) {
onlineCount++;
subscribers.add(session);
log.info("OnOpen:" + onlineCount);
}
@OnClose
public void OnClose(Session session) {
onlineCount--;
subscribers.remove(session);
log.info("OnClose:" + onlineCount);
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("OnMessage:" + message);
broadcast(message);
}
@OnError
public void onError(Session session, Throwable throwable) {
log.warning("onError:" + throwable.getMessage());
subscribers.remove(session);
onlineCount--;
}
public static void broadcast(String message) {
try {
for (Session session : subscribers) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- @Component 어노테이션이 달린 클래스는 스프링 빈에 등록되고 그 인스턴스는 싱글톤으로 스프링에 의해 관리되지만, @SeverEndPoint로 어노테이션이 달린 클래스는 WebSocket이 연결될 때마다 인스턴스가 생성되고 JWA구현에 의해 관리가 되어 내부의 @Autowried가 설정된 멤버가 정상적으로 초기화 되지 않는다.
@Autowried를 사용하기 위해서 SeverEndpointConfig.Configurator를 사용하여 SeverEndPoint의 컨텍스트에 BeanFactory 또는 ApplicationContext를 연결해 주는 작업을 하는 클래스를 생성한다.
package com.websocket.configuration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServerEndpointConfigurator extends javax.websocket.server.ServerEndpointConfig.Configurator implements ApplicationContextAware {
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ServerEndpointConfigurator.context = applicationContext;
}
}
- NotifyWS클래스의 @ServerEndpoint어노테이션 속성에 configurator = ServerEndpointConfigurator.class 속성을 추가한다.
...
@ServerEndpoint(value="/notify", configurator = ServerEndpointConfigurator.class)
public class NotifyWS {
...