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();
    }
}
}