问题
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