Spring контекст корневого приложения и путаница контекста сервлета

Я знаю, что мне нужно зарегистрировать классы, аннотированные @Controller в моем контексте сервлета, чтобы сделать мой webapp доступным. Обычно, я делаю это следующим образом:

@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 в контекст сервлета. Вот мой пример 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);
    }
}

Мне нужно использовать эту услугу в некоторых других услугах (autwire it). И теперь я в тупике:

  • Если я пытаюсь создать TimeServiceWs bean внутри корневого контекста приложения, как ожидалось, он не видит SimpMessagingTemplate bean и бросает NoSuchBeanDefinitionException
  • Если я пытаюсь создать TimeServiceWs bean внутри контекста сервлета, тогда я не могу его автоустановить к какой-либо другой службе, потому что корневой контекст не может видеть контекст сервлета beans (насколько я знаю)
  • Если я переместил все свои конфигурации в контекст сервлета, все beans будут успешно созданы, но я получаю следующее исключение: java.lang.IllegalStateException: No WebApplicationContext found и не могу получить доступ к моему webapp

Что я должен делать? Что должно быть внутри корневого контекста? Что должно быть внутри контекста сервлета? И не могли бы вы прояснить разницу между этим контекстом еще раз?

Если вам понадобится дополнительная информация, просто дайте мне знать.

Ответы

Ответ 1

Большинство Spring MVC-приложений имеют один корневой контекст, содержащий весь уровень уровня обслуживания /DAO beans, и один контекст сервлета для сервлета диспетчера приложений Spring приложения, который содержит (по крайней мере) контроллеры каждого сервлета.

Идея состоит в том, что у одного приложения может быть несколько диспетчеров сервлетов, например один для URL /shopping/*, а другой для URL /reporting/*, каждый из которых имеет собственный набор контроллеров.

Контроллеры одного диспетчера сервлетов изолированы друг от друга, что означает, что они также Spring beans, они не могут быть введены друг в друга.

Уровень сервиса и DAO beans в корневом контексте отображаются во всех контекстах сервлета, поэтому сервисный уровень beans может быть введен в любой контроллер, но не наоборот.

Корневой контекст называется родителем контекста/контекста сервлетов контроллера.

Все это означало быть механизмом выделения групп из beans друг от друга, чтобы гарантировать, что невозможны никакие взаимозависимые зависимости.

Учитывая это и просматривая вопросы:

  • Если я пытаюсь создать TimeServiceWs bean внутри корневого контекста приложения, как и ожидалось, он не видит SimpMessagingTemplate bean и бросает NoSuchBeanDefinitionException: Переместите SimpleMessagingTemplate в корневой каталог контекст, это bean, как DAO, который может быть полезен в любом месте приложения, поэтому он должен находиться в общем корневом контексте.

  • Если я пытаюсь создать TimeServiceWs bean внутри контекста сервлета, тогда я не могу его автоустановить для любой другой службы. Если это означало, что оно будет автоматически передано другим службам, оставьте его в корневом контексте.

    . Если я переместил все мои конфигурации в контекст сервлета, все beans будут успешно созданы, но я получу java.lang.IllegalStateException: нет WebApplicationContext: Сделайте обратное, переместите в основном все beans в корневой контекст и оставить в контексте сервлета только beans, которые являются специфическими для этой части приложения, во много раз только контроллеры.

Ответ 2

Конфигурация, связанная с WebSocket, относится к конфигурации DispatcherServlet так или иначе. После того, как HTTP-обращение будет обработано DispatcherServlet через его сопоставления обработчиков.

Вы должны иметь возможность использовать один контекст Spring в сценарии развертывания, в котором есть только один DispatcherServlet в веб-приложении. Консолидация конфигурации в корневом контексте имеет смысл, если использовать Spring Security, например, хотя была ошибка с AbstractAnnotationConfigDispatcherServletInitializer (см. SPR-11357). Консолидация в контексте DispatcherServlet также должна быть возможной, но вы написали, что получили исключения. Можете ли вы предоставить сведения об исключении?

Это также возможность иметь как контекст root, так и DispatcherServlet. В этом случае конфигурация WebSocket будет находиться в контексте DispatcherServlet, и невозможно установить SimpMessagingTemplate в beans в корневом контексте. Это действительно имеет смысл, поскольку есть один SimpMessagingTemplate для каждого DispatcherServlet (или другого сервлета). Нужен компонент веб-слоя, возможно, тонкая оболочка вокруг уровня сервиса beans (например, TimeServiceWs, приведенный выше), который также может быть введен с помощью SimpMessagingTemplate. Этот компонент веб-слоя по существу служит мостом.