17

I know that I need to register classes annotated with @Controller in my servlet context to make my webapp accesible. Usually, I will do it the following way:

@Configuration
@EnableWebMvc
@ComponentScan({"foo.bar.controller"})
public class WebConfig extends WebMvcConfigurerAdapter {
    //other stuff like ViewResolvers, MessageResolvers, MessageConverters, etc.
}

All other configuration classes I added to my root application context. Here is how my dispatcher initializer usually look like:

public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class, ServiceConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

But things are getting more interesting when I started to use WebSockets. To get websockets working, you have to put WebSoketConfig.class to servlet context. Here is my example of WebSocketConfig:

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
        channelRegistration.taskExecutor().corePoolSize(4).maxPoolSize(8);
    }

    @Override
    public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
        channelRegistration.taskExecutor().corePoolSize(4).maxPoolSize(8);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue", "/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }

}

Also, I've created a service to send a message to the topic:

@Service
public class TimeServiceWsImpl implements TimeServiceWs {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Override
    public void sentCurrentTime() {
        long currentTime = System.currentTimeMillis();
        String destination = "/topic/chatty";
        logger.info("sending current time to websocket /topic/time : " + currentTime);
        this.messagingTemplate.convertAndSend(destination, currentTime);
    }
}

I need to use this service in some other services (Autowire it). And now I'm in a deadlock:

  1. If I'm trying to create TimeServiceWs bean inside root application context, as expected it doesn't see SimpMessagingTemplate bean and throws NoSuchBeanDefinitionException
  2. If I'm trying to create TimeServiceWs bean inside servlet context, then I'm unable to autowire it to any another service, because root context can't see servlet context beans(as far as I know)
  3. If I move all my configurations to servlet context, all beans are successfully created, but I get the following exception: java.lang.IllegalStateException: No WebApplicationContext found and can't access my webapp

What am I supposed to do? What should be inside root context? What should be inside servlet context? And could you please clarify the difference between these context one more time please?

If you will need any additional information, just let me know.

Ashishkumar Singh
  • 3,580
  • 1
  • 23
  • 41
Oleksii Duzhyi
  • 1,203
  • 3
  • 12
  • 26

2 Answers2

26

Most Spring MVC applications have one root context containing all service layer / DAO layer beans, and one servlet context per spring dispatcher servlet of the application, which contains (at least) the controllers of each servlet.

The idea being that is that one application might have several servlet dispatchers, for example one for URL /shopping/* and the other for URL /reporting/*, each with it's own set of controllers.

The controllers of one servlet dispatcher are isolated from each other, meaning although they are also Spring beans, they cannot be injected in each other.

Service layer and DAO beans in the root context are visible in all servlet contexts, so Service layer beans can be injected in any controller, but not the other way around.

The root context is said to be the parent of the controller servlet context/contexts.

It's all meant to be a mechanism of isolating groups of beans from each other to ensure no unmeant dependencies are possible.

Given this and going through the questions:

  • If I'm trying to create TimeServiceWs bean inside root application context, as expected it doesn't see SimpMessagingTemplate bean and throws NoSuchBeanDefinitionException: Move the SimpleMessagingTemplate to the root context, it's a bean like a DAO that can be useful anywhere in the application so it should be in the shared root context.

  • If I'm trying to create TimeServiceWs bean inside servlet context, then I'm unable to autowire it to any another service: If it's meant to be autowired to other services, leave it in the root context then.

    - If I move all my configurations to servlet context, all beans are successfully created, but I get java.lang.IllegalStateException: No WebApplicationContext found: Do the opposite, move basically all beans to the root context, and leave on the servlet context only the beans that are specific of that part of the application, many times only the controllers.

Angular University
  • 42,341
  • 15
  • 74
  • 81
  • 1
    When you say "Move the SimpleMessagingTemplate to the root context"..I really dont understand how to move...i am sorry ...very new to spring ... – Saurabh Kumar Mar 27 '14 at 17:26
  • The problem is that I can't easily move SimpMessagingTemplate because Spring creates this bean and it depend on other Spring created beans. The solution I found is to move to Spring Boot - probably it registers WebSocketConfig in root application config so problem is not occurring. So the root of issue was that I was registering configs in wrong contexts. – Oleksii Duzhyi Jul 14 '15 at 18:48
8

WebSocket-related config belongs to the DispatcherServlet configuration one way or another. After all the HTTP handshake is processed by the DispatcherServlet through its handler mappings.

You should be able to go with a single Spring context in a deployment scenario where there is only one DispatcherServlet in the web application. Consolidating the configuration into the root context makes more sense if using Spring Security for example although there was a bug with the AbstractAnnotationConfigDispatcherServletInitializer (see SPR-11357). Consolidating into the DispatcherServlet context should also be possible but you wrote that you got exceptions. Can you provide the exception details?

It is also an option to have both root and DispatcherServlet contexts. In that case the WebSocket configuration will be in the DispatcherServlet context and it's not possible to inject the SimpMessagingTemplate into beans in the root context. That actually makes sense since there is one SimpMessagingTemplate to go with each DispatcherServlet (or some other servlet). What's needed is a web layer component, perhaps a thin wrapper around service layer beans (like TimeServiceWs the above example) that can also be injected with the SimpMessagingTemplate. This web layer component essentially serves as a bridge.

Rossen Stoyanchev
  • 4,910
  • 23
  • 26
  • Thanks for your answer, I think that is the point to use SimpMessagingTemplate with help of thin wrapper. – Oleksii Duzhyi Jan 24 '14 at 19:42
  • Can you explain further how this web layer component can be injected? For instance, I have a root context, my spring mvc servlet with its context and one other servlet. I'd like the message broker to be shared by all, if possible. I don't understand how to do this if SimpMessagingTemplate is not in the root context. – Jason Feb 25 '14 at 06:54
  • Jason what you're asking is a little different. One option is to configure your WebSocket Messaging in the root context -- Spring MVC can find what it needs in the root context as well and if the other Servlet is not Spring MVC it wouldn't care. However if you have two DispatcherServlets then you could extend AbstractMessageBrokerConfiguration (messaging only) and declare that in the root context. The Spring MVC mappings (i.e. the beans in WebSocketMessageBrokerConfigurationSupport) would then go to the Spring MVC configuration. Not as simple but not too hard. – Rossen Stoyanchev Mar 05 '14 at 21:13
  • Moving my config file that extends AbstractWebSocketMessageBrokerConfigurer to the root context ended up working for me as I needed to send messages to clients from my service layer in the root context. – Bal May 07 '14 at 21:15