解决Stomp(websocket)MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel

  • Post author:
  • Post category:其他




异常信息:

2020-09-22 09:42:56.581 263170 [http-nio-5556-exec-1] ERROR o.s.w.s.m.StompSubProtocolHandler - Failed to send client message to application via MessageChannel in session uddpnpiw. Sending STOMP ERROR to client.
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is java.lang.NullPointerException
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:129)
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:105)

网上找到的最靠谱的解决方法


https://stackoverflow.com/questions/61828635/occasional-nullpointerexception-when-publishing-stomp-message-using-springframew/61828913#61828913

大致说的是在早期版本中有个bug,提供解决方案的大佬Gary Russell解决这个bug后还顺便给spring-framework提了个bug;

如果ChannelInterceptor 为null就会报错!!!


下面是我的主要代码

@Slf4j
@Component
public class MyChannelInterceptor implements ChannelInterceptor {

    @Autowired
    private WebSocketRegistry webSocketRegistry;
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor =  MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if(StompCommand.CONNECT.equals(accessor.getCommand())){
            String name = accessor.getUser().getName();
            String sessionId = accessor.getSessionId();
            System.out.println("CONNECT  sessionId = " + sessionId);
            //统计用户在线数,可通过redis来实现更好
            if (webSocketRegistry.isExist(name)){
                JSONObject jsObj = new JSONObject();
                jsObj.put("sessionId",sessionId);
                messagingTemplate.convertAndSendToUser(name,"/topic/disconnect",jsObj);
            }
            webSocketRegistry.registerOnlineUser(name,sessionId);
        }else if (StompCommand.DISCONNECT.equals(accessor.getCommand())){
            Principal principal = accessor.getUser();
            //判断是否是客户端手动触发断开,根据header参数判断
            String manual = accessor.getFirstNativeHeader("manual");
            if (StringUtils.isBlank(manual)){
                if (null != principal && StringUtils.isNotBlank(principal.getName())){
                    String sessionId = accessor.getFirstNativeHeader("sessionId");
                    if (accessor.getSessionId().equals(sessionId)){
                        webSocketRegistry.unregisterOnlineUser(principal.getName(),sessionId);
                    }
                }
            }else {
                webSocketRegistry.unregisterOnlineUser(principal.getName(),accessor.getSessionId());
            }
        }
        return message;
    }


解释

:在利用websocket做一个实时聊天的基础上,我想拓展一下附加功能,配合redis实现在线人数统计,然后考虑到同一个用户发起多个连接如何处理???最后采取方案是直接建立新连接,但是与此同时通知前一个客户端断开连接;所以加了一个messagingTemplate.convertAndSendToUser的方法通知客户端,重点来了,

将messagingTemplate放入ChannelInterceptor 的preSend()方法中会导致

Caused by: java.lang.NullPointerException: null
	at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:161)
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:115)


进而引起本文最开始提到的异常


那么如何解决呢???



我仔细思考了一下,考虑可能是注入SimpMessagingTemplate 的时机不成熟,那么能不能晚点再注入,调用的时候再注入呢??能不能用懒加载的方式呢???

    @Lazy              <---------------------
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

经过反复对比测试,发现加上@Lazy注解后效果确实起到了

在初始化的时候懒加载不注入,调用的时候注入,问题解决了!!!!