Dispatcher-servlet cannot map to websocket requests

喜夏-厌秋 提交于 2019-12-21 05:15:11

问题


I'm developing a Java webapp with Spring as the main framework (Spring core, Spring mvc, Spring security, Spring data, Spring websocket are notably used).

Declaring a message-broker in a Spring context like this provides a SimpMessagingTemplate bean to the context :

<websocket:message-broker>
    <websocket:stomp-endpoint path="/stomp">
        <websocket:sockjs/>
    </websocket:stomp-endpoint>
    <websocket:simple-broker prefix="/topic,/queue"/>
</websocket:message-broker>

I have to put this tag in my root context (applicationContext.xml), otherwise services declared in that root context cannot send notifications to users via websocket (because they need the SimpMessagingTemplate).

The thing is, if I put this tag in the root context, clients get a 404 when they subscribe to websocket. And if I put the tag in the dispatcher-servlet, then services in the root context cannot send notifications since they would need the SimpMessagingTemplate (but it is only available in the child dispatcher-servlet context).

Is there a way to "bind" the dispatcher-servlet to the broker ? Declaring the bean twice is not a correct solution.

This issue is the same as Spring : how to expose SimpMessagingTemplate bean to root context ? but looking from another angle (declaring websocket in the root context instead of in the dispatcher-servlet)


回答1:


I found a dirty solution. I don't like it, but given the lack of answers on SO as well as from current and former colleagues, I had to go forward with the project and implemented a dirty fix.

The dirty fix is to Autowire the SimpMessagingTemplate in Controller and Scheduled classes (all scanned by the dispatcher-servlet, where the websocket tag is declared), and to pass the SimpMessagingTemplate as a parameter to service methods (declared in the root context).

This solution is not transparent (the SimpMessagingTemplate should be autowired directly in services ideally) but it definitely fixes the problem.




回答2:


I've write a bean to do the injection after the servlet application context inited. It will search through the parent application contexts in order to inject the SimpMessageTemplate

Whatever bean that needs the template:

@Autowired(required=false) //required=false so that it won't throw Exception when startup
private SimpMessagingTemplate messagingTemplate;

PostInjectSimpMessageTemplateBean:

Place this bean in the servlet application context (ie. the same xml file that the websocket located)

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


来源:https://stackoverflow.com/questions/36012193/dispatcher-servlet-cannot-map-to-websocket-requests

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!