弹簧根应用程序上下文和 servlet 上下文混淆

2022-09-03 00:11:20

我知道我需要注册在我的servlet上下文中注释的类,以使我的Web应用程序可访问。通常,我会按以下方式进行:@Controller

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

我添加到根应用程序上下文中的所有其他配置类。以下是我的调度程序初始值设定项通常的外观:

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[] { "/" };
    }
}

但是当我开始使用WebSockets时,事情变得越来越有趣。要使websockets正常工作,您必须将WebSoketConfig.class放在servlet上下文中。以下是我的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");
    }

}

另外,我还创建了一个服务来向主题发送消息:

@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);
    }
}

我需要在其他一些服务(自动连接)中使用此服务。现在我陷入了僵局:

  1. 如果我尝试在根应用程序上下文中创建Bean,正如预期的那样,它看不到bean并抛出TimeServiceWsSimpMessagingTemplateNoSuchBeanDefinitionException
  2. 如果我试图在servlet上下文中创建bean,那么我无法将其自动连接到任何其他服务,因为根上下文看不到servlet上下文bean(据我所知)TimeServiceWs
  3. 如果我将所有配置移动到 servlet 上下文,则所有 Bean 都已成功创建,但我得到以下异常:并且无法访问我的 webappjava.lang.IllegalStateException: No WebApplicationContext found

我该怎么办?根上下文中应该包含哪些内容?servlet 上下文中应该包含哪些内容?你能不能再澄清一下这些背景之间的区别?

如果您需要任何其他信息,请告诉我。


答案 1

大多数Spring MVC应用程序都有一个包含所有服务层/ DAO层bean的根上下文,以及一个应用程序的弹簧调度程序servlet的servlet上下文,其中(至少)包含每个servlet的控制器。

这个想法是,一个应用程序可能有几个servlet调度器,例如一个用于URL,另一个用于URL,每个都有自己的一组控制器。/shopping/*/reporting/*

一个 servlet 调度程序的控制器彼此隔离,这意味着尽管它们也是 Spring bean,但它们不能相互注入。

根上下文中的服务层和 DAO Bean 在所有 servlet 上下文中都是可见的,因此服务层 Bean 可以在任何控制器中注入,但不能相反。

根上下文被称为控制器 servlet 上下文/上下文的父级。

这一切都意味着是一种将豆组彼此隔离的机制,以确保不可能有不合理的依赖关系。

鉴于此并浏览问题:

  • 如果我尝试在根应用程序上下文中创建TimeServiceWs Bean,正如预期的那样,它看不到SimpMessagingTemplate bean并抛出NoSuchBeanDefinitionException:将 SimpleMessagingTemplate 移动到根上下文,它是一个像 DAO 一样的 Bean,可以在应用程序中的任何位置使用,因此它应该在共享的根上下文中。

  • 如果我尝试在servlet上下文中创建TimeServiceWs bean,那么我无法将其自动连接到任何其他服务:如果它打算自动连接到其他服务,请将其保留在根上下文中。

    - 如果我将所有配置移动到 servlet 上下文,则所有 bean 都已成功创建,但我得到 java.lang.IllegalStateException: No WebApplicationContext found:做相反的事情,基本上将所有bean移动到根上下文,并在servlet上下文中只保留特定于应用程序该部分的bean,很多时候只保留控制器。


答案 2

与 WebSocket 相关的配置以某种方式属于 DispatcherServlet 配置。在所有 HTTP 握手之后,调度程序Servlet 通过其处理程序映射进行处理。

您应该能够在 Web 应用程序中只有一个 DispatcherServlet 的部署场景中使用单个 Spring 上下文。例如,如果使用Spring Security,将配置合并到根上下文中会更有意义,尽管存在错误(请参阅SPR-11357)。合并到 DispatcherServlet 上下文中也应该是可能的,但是您写道您遇到了异常。您能提供例外情况详细信息吗?AbstractAnnotationConfigDispatcherServletInitializer

同时具有根上下文和调度程序Servlet上下文也是一个选项。在这种情况下,WebSocket 配置将位于 DispatcherServlet 上下文中,并且无法将 SimpMessagingTemplate 注入到根上下文中的 bean 中。这实际上是有道理的,因为每个DispatcherServlet(或其他一些servlet)都有一个SimpMessagingTemplate。我们需要的是一个Web层组件,也许是一个围绕服务层bean的薄包装器(如上面的例子中的TimeServiceWs),也可以与SimpMessagingTemplate一起注入。此 Web 层组件实质上充当桥梁。


推荐