【问题标题】:Dispatcher-servlet cannot map to websocket requestsDispatcher-servlet 无法映射到 websocket 请求
【发布时间】:2016-06-30 22:43:12
【问题描述】:

我正在开发一个以 Spring 为主要框架的 Java webapp(主要使用 Spring core、Spring mvc、Spring security、Spring data、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。如果我将标签放在 dispatcher-servlet 中,那么根上下文中的服务将无法发送通知,因为它们需要 SimpMessagingTemplate(但它仅在子 dispatcher-servlet 上下文中可用)。

有没有办法将 dispatcher-servlet “绑定”到代理?两次声明 bean 不是正确的解决方案。

这个问题和Spring : how to expose SimpMessagingTemplate bean to root context ?是一样的,但是换个角度看(在根上下文而不是dispatcher-servlet中声明websocket)

【问题讨论】:

    标签: java xml spring websocket applicationcontext


    【解决方案1】:

    我编写了一个 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();
        }
    }
    }
    

    【讨论】:

      【解决方案2】:

      我找到了一个肮脏的解决方案。我不喜欢它,但鉴于缺乏关于 SO 以及现任和前任同事的答案,我不得不推进该项目并实施了一个肮脏的修复。

      肮脏的修复是在 Controller 和 Scheduled 类中的Autowire SimpMessagingTemplate(全部由dispatcher-servlet 扫描,其中声明了websocket tag),并将SimpMessagingTemplate 作为参数传递给服务方法(在root context 中声明)。

      此解决方案不透明(理想情况下,SimpMessagingTemplate 应该直接在服务中自动装配)但它确实解决了问题。

      【讨论】:

        猜你喜欢
        • 2016-12-31
        • 1970-01-01
        • 2011-12-27
        • 2016-03-09
        • 2016-04-18
        • 2016-01-12
        • 1970-01-01
        • 2014-09-18
        • 2011-07-13
        相关资源
        最近更新 更多