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. Этот компонент веб-слоя по существу служит мостом.