Dispatcher-servlet 无法映射到 websocket 请求
Dispatcher-servlet cannot map to websocket requests
我正在开发一个 Java webapp 以 Spring 作为主要框架(Spring 核心,Spring mvc,Spring 安全,Spring 数据,Spring websocket 被特别使用)。
像这样在 Spring 上下文中声明消息代理为上下文提供了一个 SimpMessagingTemplate bean:
<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)。
问题是,如果我将此标记放在根上下文中,客户端在订阅 websocket 时会收到 404。如果我将标记放在调度程序 servlet 中,则根上下文中的服务无法发送通知,因为它们需要 SimpMessagingTemplate(但它仅在子调度程序 servlet 上下文中可用)。
有没有办法 "bind" 将 dispatcher-servlet 发送给代理?两次声明 bean 不是正确的解决方案。
此问题与相同,但从另一个角度来看(在根上下文中而不是在调度程序-servlet 中声明 websocket)
我找到了一个肮脏的解决方案。我不喜欢它,但鉴于缺乏关于 SO 以及现任和前任同事的答案,我不得不继续这个项目并实施了一个肮脏的修复。
肮脏的修复是 Autowire
Controller 和 Scheduled 类 中的 SimpMessagingTemplate
(全部由 dispatcher-servlet
扫描,其中声明 websocket tag
),并将 SimpMessagingTemplate
作为参数传递给服务方法(在 root context
中声明)。
此解决方案不透明(理想情况下 SimpMessagingTemplate
应该直接在服务中自动装配)但它确实解决了问题。
我已经编写了一个 bean 来在 servlet 应用程序上下文初始化后进行注入。它将搜索父应用程序上下文以注入 SimpMessageTemplate
需要模板的任何 bean:
@Autowired(required=false) //required=false so that it won't throw Exception when startup
private SimpMessagingTemplate messagingTemplate;
PostInjectSimpMessageTemplateBean:
将这个 bean 放在 servlet 应用上下文中(即 websocket 所在的同一个 xml 文件)
(替换"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();
}
}
}
我正在开发一个 Java webapp 以 Spring 作为主要框架(Spring 核心,Spring mvc,Spring 安全,Spring 数据,Spring websocket 被特别使用)。
像这样在 Spring 上下文中声明消息代理为上下文提供了一个 SimpMessagingTemplate bean:
<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)。
问题是,如果我将此标记放在根上下文中,客户端在订阅 websocket 时会收到 404。如果我将标记放在调度程序 servlet 中,则根上下文中的服务无法发送通知,因为它们需要 SimpMessagingTemplate(但它仅在子调度程序 servlet 上下文中可用)。
有没有办法 "bind" 将 dispatcher-servlet 发送给代理?两次声明 bean 不是正确的解决方案。
此问题与
我找到了一个肮脏的解决方案。我不喜欢它,但鉴于缺乏关于 SO 以及现任和前任同事的答案,我不得不继续这个项目并实施了一个肮脏的修复。
肮脏的修复是 Autowire
Controller 和 Scheduled 类 中的 SimpMessagingTemplate
(全部由 dispatcher-servlet
扫描,其中声明 websocket tag
),并将 SimpMessagingTemplate
作为参数传递给服务方法(在 root context
中声明)。
此解决方案不透明(理想情况下 SimpMessagingTemplate
应该直接在服务中自动装配)但它确实解决了问题。
我已经编写了一个 bean 来在 servlet 应用程序上下文初始化后进行注入。它将搜索父应用程序上下文以注入 SimpMessageTemplate
需要模板的任何 bean:
@Autowired(required=false) //required=false so that it won't throw Exception when startup
private SimpMessagingTemplate messagingTemplate;
PostInjectSimpMessageTemplateBean:
将这个 bean 放在 servlet 应用上下文中(即 websocket 所在的同一个 xml 文件)
(替换"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();
}
}
}