Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Springboot @ServerEndPoint "Failed to find the root WebApplicationContext."

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.

like image 978
Y.M. Avatar asked May 27 '15 12:05

Y.M.


People also ask

What is @springbootapplication in Spring Boot?

@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.

What does @componentscan do in Spring Boot?

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.

How do I configure spring to enable WebSocket and Stomp messaging?

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 ):

What does @enableautoconfiguration do in Spring Boot?

@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.


2 Answers

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.

like image 54
bhdrkn Avatar answered Nov 14 '22 23:11

bhdrkn


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.

like image 30
Johannes Barop Avatar answered Nov 14 '22 23:11

Johannes Barop