Java Spring 根应用程序上下文和 servlet 上下文混淆

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21319450/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 08:02:04  来源:igfitidea点击:

Spring root application context and servlet context confusion

javaspringspring-mvcwebsocket

提问by Oleksii Duzhyi

I know that I need to register classes annotated @Controllerin my servlet context to make my webapp accesible. Usualy, I do it the following way:

我知道我需要注册@Controller在我的 servlet 上下文中注释的类以使我的 webapp 可以访问。通常,我按以下方式进行操作:

@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 dispetcher initializer usualy 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:

但是当我开始使用 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");
    }

}

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 sevices (autowire it). And now I'm in deadlock:

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

  1. If I'm trying to create TimeServiceWsbean inside root application context, as expected it doesn't see SimpMessagingTemplatebean and throws NoSuchBeanDefinitionException
  2. If I'm trying to create TimeServiceWsbean 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 foundand can't access my webapp
  1. 如果我试图TimeServiceWs在根应用程序上下文中创建bean,正如预期的那样,它看不到SimpMessagingTemplatebean 并抛出NoSuchBeanDefinitionException
  2. 如果我试图TimeServiceWs在 servlet 上下文中创建bean,那么我无法将它自动​​连接到任何其他服务,因为根上下文无法看到 servlet 上下文 bean(据我所知)
  3. 如果我将所有配置移动到 servlet 上下文,则所有 bean 都已成功创建,但出现以下异常:java.lang.IllegalStateException: No WebApplicationContext found并且无法访问我的 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?

我应该做些什么?根上下文中应该包含什么?servlet 上下文中应该包含什么?您能否再澄清一次这些上下文之间的区别?

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

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

采纳答案by Angular University

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.

大多数 Spring MVC 应用程序都有一个包含所有服务层/DAO 层 bean 的根上下文,以及应用程序的每个 Spring 调度程序 servlet 一个 servlet 上下文,其中(至少)包含每个 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.

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

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.

一个servlet调度器的控制器是相互隔离的,这意味着虽然它们也是Spring bean,但它们不能相互注入。

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.

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

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

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

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

这一切都是为了将​​ bean 组彼此隔离,以确保不会产生无意义的依赖关系。

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.

  • 如果我试图在根应用程序上下文中创建 TimeServiceWs bean,正如预期的那样,它看不到 SimpMessagingTemplate bean 并抛出 NoSuchBeanDefinitionException:Move the SimpleMessagingTemplate 到根上下文,它是一个类似于 DAO 的 bean,可以在应用程序的任何地方使用,因此它应该在共享的根上下文中。

  • 如果我试图在 servlet 上下文中创建 TimeServiceWs bean,那么我无法将它自动​​装配到任何其他服务:如果它打算自动装配到其他服务,则将其留在根上下文中。

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

回答by Rossen Stoyanchev

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.

与 WebSocket 相关的配置以一种或另一种方式属于 DispatcherServlet 配置。毕竟 HTTP 握手由 DispatcherServlet 通过其处理程序映射处理。

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?

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

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.

同时拥有 root 和 DispatcherServlet 上下文也是一个选项。在这种情况下,WebSocket 配置将在 DispatcherServlet 上下文中,并且不可能将 SimpMessagingTemplate 注入根上下文中的 bean。这实际上是有道理的,因为每个 DispatcherServlet(或其他一些 servlet)都有一个 SimpMessagingTemplate。所需要的是一个 web 层组件,也许是一个围绕服务层 bean 的薄包装器(如上面的示例 TimeServiceWs),它也可以与 SimpMessagingTemplate 一起注入。这个 web 层组件本质上用作桥梁。