앞선 장들의 부품(HandshakeHandler, SockJsService, SubProtocolWebSocketHandler, 채널, 브로커)을 손으로 조립하려면 빈을 수십 개 정의해야 한다. config.annotation 패키지는 이를 두 개의 애너테이션과 콜백 인터페이스로 압축한다.
@EnableWebSocket+WebSocketConfigurer— 저수준. 사용자WebSocketHandler를 URL에 매핑하고 SockJS 폴백을 켜는 정도.@EnableWebSocketMessageBroker+WebSocketMessageBrokerConfigurer— 풀스택. STOMP 엔드포인트, 메시지 브로커,@MessageMapping디스패치까지 한 번에.
이 챕터는 두 설정 경로가 어떤 빈들을 어떻게 엮어 내는지를 본다. 핵심은 둘 다 등록 DSL → HandlerMapping 빈 패턴을 따른다는 것이다.
저수준 경로 풀스택(STOMP) 경로
───────────────────────── ─────────────────────────────────
@EnableWebSocket @EnableWebSocketMessageBroker
@Import @Import
▼ ▼
DelegatingWebSocketConfiguration DelegatingWebSocketMessageBroker
extends WebSocketConfiguration Configuration
Support extends WebSocketMessageBroker
│ ConfigurationSupport
│ 주입: List<WebSocketConfigurer> extends AbstractMessageBroker
│ Configuration (spring-messaging)
▼ │ 주입: List<WebSocketMessageBrokerConfigurer>
registerWebSocketHandlers(registry) ▼
│ registerStompEndpoints(registry)
▼ configureMessageBroker(registry) ...
ServletWebSocketHandlerRegistry ▼
│ getHandlerMapping() WebMvcStompEndpointRegistry
▼ │ getHandlerMapping()
@Bean webSocketHandlerMapping ▼
(HandlerMapping) @Bean stompWebSocketHandlerMapping
+ subProtocolWebSocketHandler 등 다수
@EnableWebSocket은 @Import(DelegatingWebSocketConfiguration.class) 한 줄이다. DelegatingWebSocketConfiguration은 WebSocketConfigurationSupport를 상속하고, 컨테이너의 모든 WebSocketConfigurer를 주입받아 registerWebSocketHandlers로 위임한다.
@Configuration(proxyBeanMethods = false)
public class DelegatingWebSocketConfiguration extends WebSocketConfigurationSupport {
@Autowired(required = false)
public void setConfigurers(List<WebSocketConfigurer> configurers) { ... }
@Override
protected void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
for (WebSocketConfigurer configurer : this.configurers)
configurer.registerWebSocketHandlers(registry);
}
}WebSocketConfigurer는 콜백 하나뿐이다.
public interface WebSocketConfigurer {
void registerWebSocketHandlers(WebSocketHandlerRegistry registry);
}WebSocketConfigurationSupport는 실제 @Bean을 정의한다. 핵심은 webSocketHandlerMapping이다.
@Bean
public HandlerMapping webSocketHandlerMapping(DefaultSockJsSchedulerContainer schedulerContainer) {
ServletWebSocketHandlerRegistry registry = initHandlerRegistry(); // 등록 DSL 채움
if (registry.requiresTaskScheduler()) {
registry.setTaskScheduler(schedulerContainer.getScheduler()); // SockJS용 스케줄러
}
return registry.getHandlerMapping(); // URL → WebSocketHttpRequestHandler 매핑 생성
}흐름은 (1) ServletWebSocketHandlerRegistry를 만들고, (2) registerWebSocketHandlers로 사용자 설정을 받아 채운 뒤, (3) registry.getHandlerMapping()으로 02장의 WebSocketHandlerMapping을 빌드하는 것이다. 한 가지 영리한 점은 DefaultSockJsSchedulerContainer다 — SockJS를 쓰는 핸들러가 하나라도 등록됐을 때만(requiresTaskScheduler()) ThreadPoolTaskScheduler(스레드명 SockJS-, 풀 크기 = CPU 수)를 만들어 주입한다. SockJS heartbeat·세션 정리에 필요한 스케줄러(03장)를 사용자가 직접 제공하지 않아도 자동으로 채워 주는 것이다.
DSL은 두 계층이다. WebSocketHandlerRegistry(여러 핸들러 등록)와 WebSocketHandlerRegistration(한 핸들러의 옵션).
// 사용자 코드 예시
registry.addHandler(myHandler(), "/echo") // WebSocketHandlerRegistration 반환
.setAllowedOrigins("https://example.com")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS(); // SockJsServiceRegistration 반환AbstractWebSocketHandlerRegistration이 이 옵션들을 모으고, getMappings()에서 두 갈래로 매핑을 만든다.
protected final M getMappings() {
M mappings = createMappings();
if (this.sockJsServiceRegistration != null) { // .withSockJS() 호출됨
SockJsService sockJsService = ...getSockJsService(); // DefaultSockJsService
// path → "/path/**" 패턴으로 SockJsHttpRequestHandler 매핑
addSockJsServiceMapping(mappings, sockJsService, wsHandler, pathPattern);
}
else { // 순수 WebSocket
HandshakeHandler handshakeHandler = getOrCreateHandshakeHandler(); // DefaultHandshakeHandler
HandshakeInterceptor[] interceptors = getInterceptors(); // +OriginHandshakeInterceptor
addWebSocketHandlerMapping(mappings, wsHandler, handshakeHandler, interceptors, path);
}
return mappings;
}withSockJS()를 부르면 SockJsServiceRegistration이 생기고, 매핑은 SockJS 경로 전체(/path/**)를 받는 SockJsHttpRequestHandler로 만들어진다(03장). 부르지 않으면 단일 경로를 02장의 WebSocketHttpRequestHandler로 매핑한다. 어느 경우든 getInterceptors()가 사용자 인터셉터 뒤에 OriginHandshakeInterceptor를 자동으로 덧붙여 Origin 보안 기본값을 보장한다. ServletWebSocketHandlerRegistry/ServletWebSocketHandlerRegistration이 Servlet 환경용 구체 구현이다.
@EnableWebSocketMessageBroker는 @Import(DelegatingWebSocketMessageBrokerConfiguration.class)다. 이 설정은 WebSocketMessageBrokerConfigurationSupport(→ spring-messaging의 AbstractMessageBrokerConfiguration)를 상속해, 04장에서 본 STOMP 풀스택을 통째로 부트스트랩한다. WebSocketMessageBrokerConfigurer로 커스터마이즈한다(STOMP 엔드포인트, 브로커, 인바운드/아웃바운드 채널, 메시지 컨버터 등).
WebSocketMessageBrokerConfigurationSupport가 정의하는 핵심 빈들:
@Bean public WebSocketHandler subProtocolWebSocketHandler(inbound, outbound) {
return new SubProtocolWebSocketHandler(inbound, outbound); // 04장의 브리지
}
@Bean public HandlerMapping stompWebSocketHandlerMapping(subProtocolWebSocketHandler, scheduler, inbound) {
WebSocketHandler handler = decorateWebSocketHandler(subProtocolWebSocketHandler); // 데코레이터 팩토리 적용
WebMvcStompEndpointRegistry registry = new WebMvcStompEndpointRegistry(handler, ...);
registerStompEndpoints(registry); // 사용자 콜백: registry.addEndpoint("/portfolio").withSockJS()
return registry.getHandlerMapping(); // STOMP 엔드포인트 URL → 핸들러
}
@Bean public WebSocketMessageBrokerStats webSocketMessageBrokerStats(...) { ... } // 운영 통계subProtocolWebSocketHandler— 04장의SubProtocolWebSocketHandler빈. 채널 두 개를 받아 STOMP 핸들러를 단다(STOMP 핸들러 자체는 상위AbstractMessageBrokerConfiguration쪽 설정과 엮인다).stompWebSocketHandlerMapping—StompEndpointRegistryDSL로 받은 STOMP 엔드포인트(addEndpoint("/portfolio"))를 URL 매핑으로 변환.decorateWebSocketHandler가 등록된WebSocketHandlerDecoratorFactory들을 차례로 적용해 핸들러를 감싼다 — 사용자 정의 데코레이터(예: 인증 후처리)를 STOMP 핸들러에 주입하는 확장점이다.webSocketMessageBrokerStats— 04장의 세션 통계, 채널 실행기, SockJS 스케줄러 상태를 한데 모아 노출하는 운영용 빈.
StompEndpointRegistry / StompWebSocketEndpointRegistration은 STOMP 엔드포인트 등록 DSL이고, WebSocketTransportRegistration은 메시지 크기 한도·전송 시간 한도·WebSocketHandlerDecoratorFactory 등 전송 계층 옵션을 모은다.
여러 인프라 빈(SubProtocolWebSocketHandler, WebSocketHandlerMapping, WebMvcStompEndpointRegistry가 만든 매핑 등)이 SmartLifecycle이며, @EnableWebSocketMessageBroker 구성에서는 phase를 0으로 맞춘다(setPhase(getPhase())). 시작/정지 순서를 일관되게 정렬해, 채널 구독·세션 종료가 올바른 타이밍에 일어나도록 보장한다.
WebSocketMessageBrokerConfigurationSupport는 webSocketScopeConfigurer 빈으로 "websocket" 스코프(SimpSessionScope)를 등록한다(SCOPE_WEBSOCKET, 7.0+). 이 스코프의 빈은 WebSocket/STOMP 세션 동안만 살아 있어, 세션별 상태를 컨테이너 관리 빈으로 깔끔히 표현할 수 있다.
@EnableWebSocketMessageBroker
│ @Import
▼
DelegatingWebSocketMessageBrokerConfiguration
│ 주입: List<WebSocketMessageBrokerConfigurer>
│
├─ configureMessageBroker(MessageBrokerRegistry) ← 브로커/목적지 프리픽스
├─ registerStompEndpoints(StompEndpointRegistry) ← addEndpoint("/portfolio").withSockJS()
├─ configureClientInboundChannel / Outbound... ← 채널 인터셉터/스레드풀
└─ configureWebSocketTransport(...) ← 메시지 한도, 데코레이터 팩토리
│
▼ @Bean 생성
┌────────────────────────────────────────────────────────────┐
│ clientInboundChannel / clientOutboundChannel / brokerChannel │ (spring-messaging)
│ SubProtocolWebSocketHandler (STOMP 브리지) │
│ StompSubProtocolHandler │
│ SimpleBroker 또는 StompBrokerRelay │
│ WebSocketAnnotationMethodMessageHandler (@MessageMapping) │
│ stompWebSocketHandlerMapping (URL → 핸들러) │
│ webSocketMessageBrokerStats / websocket 스코프 │
└────────────────────────────────────────────────────────────┘
│ 애플리케이션 기동(refresh) → SmartLifecycle.start (phase 0)
▼
채널 구독 활성화, 엔드포인트 매핑 라이브 → 클라이언트 연결 수락
WebSocketConfigurationSupport.webSocketHandlerMapping— 등록 DSL을 채워WebSocketHandlerMapping빈을 생성. SockJS 스케줄러를 조건부 자동 주입.AbstractWebSocketHandlerRegistration.getMappings—withSockJS()여부로 SockJS 매핑 vs 순수 WebSocket 매핑을 분기 생성, Origin 인터셉터 자동 추가.WebSocketMessageBrokerConfigurationSupport.stompWebSocketHandlerMapping/subProtocolWebSocketHandler— STOMP 풀스택 빈 구성과 데코레이터 팩토리 적용.
- Configurer 콜백 패턴:
@Enable*→Delegating*Configuration이 컨테이너의 모든*Configurer를 주입·위임. 여러 설정 클래스가 협력적으로 구성을 보탤 수 있다(spring-webmvc의WebMvcConfigurer와 동일한 관용구). - DSL → HandlerMapping 빈: 사용자는 유창한 DSL로 의도만 적고, 복잡한 빈 그래프 조립은
*Support가 책임진다. - 확장 훅:
WebSocketHandlerDecoratorFactory(핸들러 감싸기),setHandshakeHandler/addInterceptors(핸드셰이크 커스터마이즈),configureWebSocketTransport(전송 한도),"websocket"스코프(세션 빈). - 합리적 기본값: Origin 인터셉터 자동 추가, SockJS 스케줄러 자동 생성, Lifecycle phase 정렬 — 설정을 최소화하면서도 안전·정합성을 확보.
설정 패키지는 앞 장들의 부품을 두 진입점으로 묶는다. @EnableWebSocket은 사용자 WebSocketHandler를 URL에 매핑(SockJS 옵션 포함)하는 저수준 경로, @EnableWebSocketMessageBroker는 STOMP 엔드포인트·채널·브로커·@MessageMapping까지 엮는 풀스택 경로다. 둘 다 *Configurer 콜백으로 의도를 받아 *ConfigurationSupport가 HandlerMapping을 비롯한 빈 그래프로 구체화하며, Origin 보안·SockJS 스케줄러·Lifecycle 정렬 같은 기본값을 자동으로 채워 준다.