Диспетчер-сервлет не может сопоставить запросы websocket
Я занимаюсь разработкой на Java веб-приложения с весны, как главной основы (сердечника весны, Весна в MVC, Весна безопасности, весенний данные, Весна WebSocket, которая явно используется).
Объявление брокера сообщений в контексте Spring, как это, предоставляет Боб SimpMessagingTemplate контексту:
<websocket:message-broker>
<websocket:stomp-endpoint path="/stomp">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic,/queue"/>
</websocket:message-broker>
Я должен поместить этот тег в мой корневой контекст (applicationContext.xml), в противном случае службы, объявленные в этом корневом контексте, не могут отправлять уведомления пользователям через websocket (поскольку им требуется SimpMessagingTemplate).
Дело в том, что если я помещаю этот тег в корневой контекст, клиенты получают 404, когда они подписываются на websocket. И если я помещу тег в dispatcher-servlet, то службы в корневом контексте не смогут отправлять уведомления, поскольку им потребуется SimpMessagingTemplate (но он доступен только в дочернем контексте dispatcher-servlet).Есть ли способ "привязать" диспетчер-сервлет к брокеру ? Объявление Боба дважды не является правильным решение.
Эта проблема такая же, как и Spring : как предоставить simpmessagingtemplate bean корневому контексту ? но с другой стороны (объявление websocket в корневом контексте, а не в dispatcher-servlet)
2 ответа:
Я нашел грязное решение. Мне это не нравится,но учитывая отсутствие ответов на SO, а также от нынешних и бывших коллег, я должен был идти вперед с проектом и реализовал грязное исправление.
Грязное исправление состоит в том, чтобы
Autowire
SimpMessagingTemplate
в контроллере и запланированных классах (все сканируютсяdispatcher-servlet
, где объявленwebsocket tag
), и передатьSimpMessagingTemplate
в качестве параметра в методы обслуживания (объявленные вroot context
).Это решение непрозрачно (
SimpMessagingTemplate
должно быть автоматически подключено непосредственно в сервисах в идеале), но это определенно исправляет проблему.
Я написал Боб, чтобы сделать инъекцию после контекста приложения сервлета inited. Он будет искать в контекстах родительского приложения, чтобы ввести SimpMessageTemplate
Любой Боб, которому нужен шаблон:
@Autowired(required=false) //required=false so that it won't throw Exception when startup private SimpMessagingTemplate messagingTemplate;
PostInjectSimpMessageTemplateBean:
Поместите этот боб в контекст приложения сервлета (т. е. тот же xml-файл, который находится в websocket)
(заменить "YOUR.PACKAGE.NAME")
public class PostInjectSimpMessageTemplateBean implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { ApplicationContext servletContext = event.getApplicationContext(); ApplicationContext context = servletContext.getParent(); SimpMessagingTemplate template = servletContext.getBean(SimpMessagingTemplate.class); while(context != null){ for(String beanName : context.getBeanDefinitionNames()){ Object bean = context.getBean(beanName); Class<?> clazz = bean.getClass(); if(!clazz.getName().startsWith("YOUR.PACKAGE.NAME")) continue; List<FieldWithAnnotation<Autowired>> fields = ReflectionUtils.findFieldsWithAnnotation(clazz, Autowired.class); for (FieldWithAnnotation<Autowired> fieldWithAnno : fields) { Field field = fieldWithAnno.getField(); if(field.getType() == SimpMessagingTemplate.class){ field.setAccessible(true); try { field.set(bean, template); } catch (Exception e) {} } } List<Method> methods = ReflectionUtils.findMethodsWithAnnotation(clazz, Autowired.class); for (Method method : methods) { Class<?>[] paramtypes = method.getParameterTypes(); if(paramtypes.length == 1){ if(paramtypes[0] == SimpMessagingTemplate.class){ method.setAccessible(true); try { method.invoke(bean, template); } catch (Exception e) {} } } } } context = context.getParent(); } } }