[Spring] Failed to start bean 'subProtocolWebSocketHandler' 에러 해결
에러 발생
Spring에서 WebSocket을 이용해서 테스트 코드를 작성 하던 중 에러가 발생했다.
org.springframework.context.ApplicationContextException: Failed to start bean 'subProtocolWebSocketHandler'; nested exception is java.lang.IllegalArgumentException: No handlers
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:170) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.9.jar:5.3.9]
at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_301]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.4.jar:2.5.4]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) [spring-boot-2.5.4.jar:2.5.4]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) [spring-boot-2.5.4.jar:2.5.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) [spring-boot-2.5.4.jar:2.5.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) [spring-boot-2.5.4.jar:2.5.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) [spring-boot-2.5.4.jar:2.5.4]
at com.example.demo.OcppForJavaApplication.main(OcppForJavaApplication.java:10) [main/:na]
Caused by: java.lang.IllegalArgumentException: No handlers
at org.springframework.util.Assert.isTrue(Assert.java:121) ~[spring-core-5.3.9.jar:5.3.9]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.start(SubProtocolWebSocketHandler.java:270) ~[spring-websocket-5.3.9.jar:5.3.9]
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.9.jar:5.3.9]
... 15 common frames omitted
Process finished with exit code 1
에러를 읽어보면 'subProtocolWebSocketHandler'를 만들다 에러가 난 것 같다.
원인 분석
위 에러에서 아래줄을 읽어보면 No handlers라는 메시지가 보이고 아래로 에러 스택을 확인 할 수 있다. 타고 들어가면 SubProtocolWebSocketHandler 클래스에 start 메소드에서 defaultProtocolHandler나 protocolHandlers가 비어있다면 발생하는 에러라는 것을 알 수 있다.
@Override
public final void start() {
Assert.isTrue(this.defaultProtocolHandler != null || !this.protocolHandlers.isEmpty(), "No handlers");
synchronized (this.lifecycleMonitor) {
this.clientOutboundChannel.subscribe(this);
this.running = true;
}
}
defaultProtocolHandler는 기본적으로 @Nullable 메소드가 붙어 있다.
@Nullable
private SubProtocolHandler defaultProtocolHandler;
public void setDefaultProtocolHandler(@Nullable SubProtocolHandler defaultProtocolHandler) {
this.defaultProtocolHandler = defaultProtocolHandler;
if (this.protocolHandlerLookup.isEmpty()) {
setProtocolHandlers(Collections.singletonList(defaultProtocolHandler));
}
}
그럼 얘는 Null일수도 있으니 넘기고 다음 핸들러를 살펴보자.
private final Set<SubProtocolHandler> protocolHandlers = new LinkedHashSet<>();
public void addProtocolHandler(SubProtocolHandler handler) {
List<String> protocols = handler.getSupportedProtocols();
if (CollectionUtils.isEmpty(protocols)) {
if (logger.isErrorEnabled()) {
logger.error("No sub-protocols for " + handler);
}
return;
}
for (String protocol : protocols) {
SubProtocolHandler replaced = this.protocolHandlerLookup.put(protocol, handler);
if (replaced != null && replaced != handler) {
throw new IllegalStateException("Cannot map " + handler +
" to protocol '" + protocol + "': already mapped to " + replaced + ".");
}
}
this.protocolHandlers.add(handler);
}
얘는 Set형식으로 된 핸들러들의 집합인 것 같다. addProtocolHandler를 통해서 직접 핸들러를 추가 해 줄 수 있는 것 같다. 이 함수를 호출 하는 곳을 살펴보자
WebMvcStompEndpointRegistry클래스의 addEndpoint 메소드에서 핸들러를 추가해 주는것을 확인 할 수 있다.
@Override
public StompWebSocketEndpointRegistration addEndpoint(String... paths) {
this.subProtocolWebSocketHandler.addProtocolHandler(this.stompHandler);
WebMvcStompWebSocketEndpointRegistration registration =
new WebMvcStompWebSocketEndpointRegistration(paths, this.webSocketHandler, this.sockJsScheduler);
this.registrations.add(registration);
return registration;
}
WebMvcStompEndpointRegistry는 StompEndpointRegistry를 구현하고 있고 addEndpoint 메소드를 Override 한 것을 확인 할 수 있다.
그럼 StompEndPointRegistry를 통해서 addEndpoint를 호출 할 수 있다는 말이 되지않을까?
해결
WebSocketMessageBrokerConfigurer를 구현하면 registerStompEndpoints 메소드를 Overrid 할 수 있다.
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
WebSocketMessageBrokerConfigurer.super.registerStompEndpoints(registry);
}
이 메소드가 StompEndPointRegistry를 매개변수로 받으니 여기서 addEndpoint 메소드를 호출해서 핸들러를 임시로 등록 할 수 있지 않을까 생각한다.
registry.addEndpoint("/test");
위와 같은 줄을 추가해해주면 일단 에러는 해결이 되었다!!
클라이언트측에서 addEndpoint에 등록된 경로를 통해서 소켓을 만들면 연결이 만들어 진다!