I'm having trouble using spring with @ServerEndPoint annotated class
i'm using Springboot 1.2.3 and i'm trying to figure it out how to have a single instance of the endpoint
@SpringBootApplication
@EnableJpaRepositories
@EnableWebSocket
public class ApplicationServer {
public static void main(String[] args) {
SpringApplication.run(ApplicationServer.class, args);
}
}
Spring configuration:
@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {
@Bean
public ServerEndPoint serverEndpoint() {
return new ServerEndPoint();
}
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocket endpoint:
@ServerEndpoint(value = "/", decoders = MessageDecoder.class,
encoders = MessageEncoder.class, configurator = SpringConfigurator.class)
public class ServerEndPoint {
private static final Logger LOG = LoggerFactory.getLogger(ServerEndPoint.class);
public static final Set<CommunicationObserver> OBSERVERS = Sets.newConcurrentHashSet();
@OnMessage
public void onMessage(Session session, Message msg) {
LOG.debug("Received msg {} from {}", msg, session.getId());
for (CommunicationObserver o : OBSERVERS) {
o.packetReceived(session, msg);
}
}
This is based on the Spring WebSocket JSR-356 tutorial, but i've got the following error:
java.lang.IllegalStateException: Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?
at org.springframework.web.socket.server.standard.SpringConfigurator.getEndpointInstance(SpringConfigurator.java:68)
at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:50)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:138)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:687)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
I have both tested in embedded mode and with external tomcat 8 and jetty 9 (in external mode, i remove de Spring config file) but the same error appeares.
the only workaround i've found is to create a custom configurator.
public class SpringEndpointConfigurator extends ServerEndpointConfig.Configurator {
private static WebApplicationContext wac;
public SpringEndpointConfigurator() {
}
public SpringEndpointConfigurator(WebApplicationContext wac) {
SpringEndpointConfigurator.wac = wac;
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
T endPoint = wac.getAutowireCapableBeanFactory().getBean(endpointClass);
return (endPoint != null) ? endPoint : wac.getAutowireCapableBeanFactory().createBean(endpointClass);
}
it is created as a @Bean with the parameterized constructor.
I must have missed something to get it done with the SpringConfigurator class, but i don't know what.
@SpringBootApplication is a convenience annotation that adds all of the following: @Configuration: Tags the class as a source of bean definitions for the application context. @EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
For example, if spring-webmvc is on the classpath, this annotation flags the application as a web application and activates key behaviors, such as setting up a DispatcherServlet. @ComponentScan: Tells Spring to look for other components, configurations, and services in the com/example package, letting it find the controllers.
Now that the essential components of the service are created, you can configure Spring to enable WebSocket and STOMP messaging. Create a Java class named WebSocketConfig that resembles the following listing (from src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java ):
@EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings. For example, if spring-webmvc is on the classpath, this annotation flags the application as a web application and activates key behaviors, such as setting up a DispatcherServlet.
SpringConfigurator
uses ContextLoader
to obtain spring context. Spring Boot does set up the ServletContext but it never uses ContextLoaderListener
which initializes ContextLoader
to hold static state of spring context. You can try to add ContextLoaderListener
or as a workaround you can write your own context holder and configurator.
Here is an example:
First context holder and configurator:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.websocket.server.ServerEndpointConfig;
public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
/**
* Spring application context.
*/
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 {
CustomSpringConfigurator.context = applicationContext;
}
}
To get context we need to configure this as Bean:
@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {
...
@Bean
public CustomSpringConfigurator customSpringConfigurator() {
return new CustomSpringConfigurator(); // This is just to get context
}
}
Then you need to set configurator correctly:
@ServerEndpoint(value = "/", decoders = MessageDecoder.class,
encoders = MessageEncoder.class, configurator = CustomSpringConfigurator.class)
public class ServerEndPoint {
...
}
As a side note, yes if you delete SpringConfigurator
your application will start and you can handle request. But you cannot autowire other beans.
With Spring Boot the Spring context is not loaded via SpringContextLoaderListener
which is required by the SpringConfigurator.class
.
Thats why the endpoints and and ServerEndpointExporter
beans are needed.
The only thing you have to do to get your example working is removing the SpringConfigurator.class
from the @ServerEndPoint
endpoint definition.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With