最近有一個需求,前臺使用WebSocket請求後臺,後臺給其他頁面推送數據。
有這樣的需求,勢必要在我編寫的WebSocket層去調用我的Service層去訪問數據庫,於是我就寫出瞭如下的代碼:
@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
private static ConcurrentHashMap<String, MyWebSocket> map = new ConcurrentHashMap<>();
private Session session;
@Autowired
private MyService myServiceImpl;
....
}
看起來一切都沒有問題,但是當我運行的時候,我發現我的myServiceImpl是空!
結果去網上查了一下,發現在@ServerEndPoint中是無法使用@Autowired注入Bean的。
原理大概是這樣:
其實在項目啓動的時候,這個MyWebSocket已經被注入了這個MyService的Bean,但是WebSocket的特點就是每建立一個連接,會生成一個新的MyWebSocket對象,網上有人分析過源碼,也可以理解成new了一個WebSocket,這樣當然是不能獲得自動注入的對象了。
所以我的解決方案大概就是獲取到Spring容器,然後去進行手動注入。
因爲新生成的MyWebSocket對象根本不存在與Spring容器中,所以傳統的ContextLoader.getCurrentWebApplicationContext()自然也就無法使用。
我用到了ApplicationContextAware來在項目加載時就獲得項目的Spring容器。代碼如下
@Component
@Lazy(false)
public class MyApplicationContextAware implements ApplicationContextAware {
private static ApplicationContext APPLICATION_CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
APPLICATION_CONTEXT = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
}
這樣我們就可以在任何類中去拿到這個類的ApplicationContext
通過這個ApplicationContext我們就可以在新生成一個WebSocket對象時來手動注入Service層了
@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
private static ConcurrentHashMap<String, MyWebSocket> map = new ConcurrentHashMap<>();
private Session session;
private MyService myServiceImpl = (MyService) MyApplicationContextAware.getApplicationContext().getBean("myServiceImpl");
這樣問題就解決了!