最近在做一個web terminal的需求,自己也寫了 demo ,使用 websocket + stomp 進行前后端通訊,其中遇到一個問題,就是我的前后端連接正常及 ssh 連接也正常了,但是我需要把 ssh 連接返回的信息,再返回給客戶端。了解到使用 SimpMessagingTemplate ,但是在使用過程中卻遇到一個循環依賴的問題,所以這里記錄一下。
一、SimpMessagingTemplate的作用
1、SimpMessagingTemplate可以在應用的任意地方發送消息。
Spring的SimpMessagingTemplate能夠在應用的任何地方發送消息,甚至不必以首先接收一條消息作為前提。使用SimpMessagingTemplate的最簡單方式是將它(或者其接口SimpMessageSendingOperations)自動裝配到所需的對象中。
2、SimpMessagingTemplate可以為指定的用戶發送消息。
SimpMessagingTemplate還提供了convertAndSendToUser()方法。convertAndSendToUser()方法能夠讓我們給特定用戶發送消息。
simpMessageSendingOperations.convertAndSendToUser("1", "/message", "測試convertAndSendToUser"); stomp.subscribe('/users/1/message', function(message){ });
客戶端接收一對一消息的主題是"/users/"+usersId+"/message",這里的用戶Id可以是一個普通字符串,只要每個客戶端都使用自己的Id並且服務器端知道每個用戶的Id就行了。
二、如何使用SimpMessagingTemplate將websocket消息發送給客戶端
1、引入 websocket 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、配置registry
@Configuration @Slf4j @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry ) { //路徑"/web-terminal"被注冊為STOMP端點,對外暴露,客戶端通過該路徑接入WebSocket服務
registry.addEndpoint("web-terminal").setAllowedOrigins("*"); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 用戶可以訂閱來自以"/topic"為前綴的消息,客戶端只可以訂閱這個前綴的主題
config.enableSimpleBroker("/topic/1024"); // 客戶端發送過來的消息,需要以"/xterm"為前綴,再經過Broker轉發給響應的Controller
config.setApplicationDestinationPrefixes("/xterm"); } }
3、使用SimpMessagingTemplate將websocket消息發送給客戶端
// 注入SimpMessagingTemplate 調用convertAndSend來推送消息 // 1、注入
@Autowired private SimpMessagingTemplate simpMessagingTemplate; // 2、使用 //如果沒有數據來,線程會一直阻塞在這個地方等待數據。
while ((i = inputStream.read(buffer)) != -1) { System.out.println(new java.lang.String(Arrays.copyOfRange(buffer, 0, i))); template.convertAndSend("/topic/1024", new String(Arrays.copyOfRange(buffer, 0, i))); }
三、解決SimpMessagingTemplate循環依賴問題
1、問題背景
在這個類上注入 SocketService,SocketService 里有我們的業務處理,在 SocketService 里我們需要將消息發送給客戶端,所以需要注入 SimpMessagingTemplate, 但是一運行就會報錯 循環依賴 的問題。
猜測可能是 WebSocketConfig 實現的 WebSocketMessageBrokerConfigurer 里有實現 SimpMessagingTemplate,那么 WebSocketConfig 依賴 SocketService,反過來 SocketService 又依賴 SimpMessagingTemplate,所以導致了循環依賴問題。
2、解決方案
根據 spring 的描述,基於構造器的循環依賴是沒法解決的,官方文檔都攤牌了,你想讓構造器注入支持循環依賴,是不存在的,不如把代碼改了。所以解決方法就是可以改造一下代碼。
在 Controller 層去注入 SimpMessagingTemplate,然后將其作為參數傳給 Service層的 SocketService 里的方法去調用。
// Controller 層注入及傳參
@Controller @AllArgsConstructor public class SocketController { private SocketService socketService; private SimpMessagingTemplate template; @MessageMapping("/msg") public void send(WebSSHData webSSHData) { System.out.println(webSSHData.getOperate()); socketService.handlerMsg(webSSHData, template); } } // Service 層拿到參數去處理
public void handlerMsg(WebSSHData webSSHData, SimpMessagingTemplate template) { ...... connectToSSH(sshConnectInfo, finalWebSSHData, template); } // 在 connectToSSH 里使用
template.convertAndSend("/topic/1024", new String(Arrays.copyOfRange(buffer, 0, i)));
這樣確實就解決了循環依賴的問題,消息也順利的發送給了客戶端。