解决SSH的WebSocket注入
公司有个2010年开始的旧项目,由于业务变更,需要添加WebSocket模块,经过几番折腾,解决了无法Spring注入,也解决了HTTP Session无法获取的问题(百度上几乎都是访问WebSocket的时候进行HTTP Session生成,但公司业务上并非用WebSocket生成,而是在WebSocket之前就有htp请求,因此采用另一种写法),使用的是Spring3的版本
1。先生成一个HTTP SESSION CONTEXT单例进行Session保存
```
/**
* HttpSessionContext.java 1.00 20200106
*/
package com.dph.websocket.context;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
/**
* 20200106:Add by Dely<br/>
* HttpSessionContext 单例session监听
* @author Dely
* @date 2020年1月6日 add
* @version 1.0
*/
public class HttpSessionContext {
private final ConcurrentMap<String, HttpSession> sessionMap;
private HttpSessionContext() {
sessionMap = new ConcurrentHashMap<String, HttpSession>(64);
}
private static class Instance {
private static final HttpSessionContext instace = new HttpSessionContext();
}
public static final HttpSessionContext getInstance() {
return Instance.instace;
}
/**
* 20190318:Add by dph<br/>
* Function: add 增加session缓存
* @date 2019年3月18日 add
* @author dph
* @param session
*/
public void add(final HttpSession session) {
if(null != session) {
sessionMap.put(session.getId(), session);
}
}
/**
* 20190318:Add by dph<br/>
* Function: delete 删除session缓存
* @date 2019年3月18日 add
* @author dph
* @param session
*/
public void delete(final HttpSession session) {
if(null != session) {
sessionMap.remove(session.getId());
}
}
/**
* 20190318:Add by dph<br/>
* Function: get 根据id获取session缓存
* @date 2019年3月18日 add
* @author dph
* @param sessionID
* @return
*/
public HttpSession get(final String sessionID) {
if(StringUtils.isEmpty(sessionID)) {
return null;
}
return sessionMap.get(sessionID);
}
}
```
2. 配置web监听
```
/**
* HttpSessionListener.java 1.00 20200106
*/
package com.dph.websocket.listener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import com.dph.websocket.context.HttpSessionContext;
/**
* 20200106:Add by Dely<br/>
* HttpSessionListener
* @author Dely
* @date 2020年1月6日 add
* @version 1.0
*/
@WebListener
public class HttpSessionListener implements javax.servlet.http.HttpSessionListener {
private final HttpSessionContext context = HttpSessionContext.getInstance();
/**
* Notification that a session was created.
* @param se the notification event
*/
@Override
public void sessionCreated(HttpSessionEvent se) {
context.add(se.getSession());
}
/**
* Notification that a session is about to be invalidated.
* @param se the notification event
*/
@Override
public void sessionDestroyed(HttpSessionEvent se) {
context.delete(se.getSession());
}
}
```
3. 在WebSocket中配置注解
```
/**
* HttpSessionConfigurator.java 1.00 20200106
*/
package com.dph.websocket.config;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import com.dph.websocket.context.HttpSessionContext;
/**
* 20200106:Add by Dely<br/>
* HttpSessionConfigurator
* @author Dely
* @date 2020年1月6日 add
* @version 1.0
*/
public class HttpSessionConfigurator extends Configurator {
private final transient Log log = LogFactory.getLog(HttpSessionConfigurator.class);
private static final Map<String, Map<Class<?>, String>> CACHE = new ConcurrentHashMap<String, Map<Class<?>, String>>();
private static final String NIL = ObjectUtils.identityToString(new Object());
/* (non-Javadoc)
* @see javax.websocket.server.ServerEndpointConfig.Configurator#modifyHandshake(javax.websocket.server.ServerEndpointConfig, javax.websocket.server.HandshakeRequest, javax.websocket.HandshakeResponse)
*/
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(sec, request, response);
String sessionId = "";
//验证子协议
Map<String, List<String>> reqHeaders = request.getHeaders();
if(reqHeaders.containsKey(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL)) {
final List<String> subProtocolList = reqHeaders.get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
final String[] protocols = subProtocolList.get(0).split(", ");
if(0 < protocols.length) {
// 对JSSESSION ID进行重上传
// 此部分是对于已通过HTTP(S)等其他请求生成过JSSESSION ID,
// 将这部分进行携带获取服务器HTTP SESSION
// 验证协议通过
Map<String, List<String>> headers = response.getHeaders();
List<String> list = new ArrayList<String>();
list.add(protocols[0]);
headers.put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, list);
// JSESSIONID当成子协议的首个值
sessionId = protocols[1];
}
}
//对每个进行握手的用户都配置请求
Map<String, Object> userProperties = sec.getUserProperties();
userProperties.put(HttpSessionContext.class.getName(), sessionId);
userProperties.put(HandshakeRequest.class.getName(), request);
}
/* (non-Javadoc)
* @see javax.websocket.server.ServerEndpointConfig.Configurator#getEndpointInstance(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
if( null == wac) {
final String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?";
log.error(message);
throw new IllegalStateException(message);
}
// 从配置中寻找endpoint(是类名小写的)
String beanName = ClassUtils.getShortNameAsProperty(endpointClass);
if(wac.containsBean(beanName)) {
T endpoint = wac.getBean(beanName, endpointClass);
if(log.isTraceEnabled()) {
log.trace(MessageFormat.format("Using @ServerEndpoint singleton {0}", endpoint));
}
return endpoint;
}
// 从注解中寻找endpoint
final Component annot = AnnotationUtils.findAnnotation(endpointClass, Component.class);
if(null != annot && wac.containsBean(annot.value())) {
T endpoint = wac.getBean(annot.value(), endpointClass);
if(log.isTraceEnabled()) {
log.trace(MessageFormat.format("Using @ServerEndpoint singleton {0}", endpoint));
}
return endpoint;
}
// 从配置中寻找endpoint(bean名称是自定义的)
beanName = getBeanNameByType(wac, endpointClass);
if(null != beanName) {
return (T) wac.getBean(beanName);
}
// 都找不到,重新创建一个
return wac.getAutowireCapableBeanFactory().createBean(endpointClass);
}
/**
* 20200106:Add by Dely<br/>
* Function: 获取自定义名称的bean,并缓存
* @date 2020年1月6日 add
* @author Dely
* @param wac
* @param endpointClass
* @return
*/
private String getBeanNameByType(WebApplicationContext wac, Class<?> endpointClass) {
String wacId = wac.getId();
Map<Class<?>, String> beanNamesByType = CACHE.get(wacId);
if(null == beanNamesByType) {
beanNamesByType = new ConcurrentHashMap<Class<?>, String>();
CACHE.put(wacId, beanNamesByType);
}
if(!beanNamesByType.containsKey(endpointClass)) {
final String[] names = wac.getBeanNamesForType(endpointClass);
if(1 == names.length) {
beanNamesByType.put(endpointClass, names[0]);
}
else {
beanNamesByType.put(endpointClass, NIL);
if(1 < names.length) {
final String message = MessageFormat.format("Found multiple @ServerEndpoint's of type {0}, names={1}", endpointClass, Arrays.toString(names));
log.error(message);
throw new IllegalStateException(message);
}
}
}
final String beanName = beanNamesByType.get(endpointClass);
return NIL.equals(beanName) ? null : beanName;
}
}
```
4. WebSocket业务
```
/**
* WebSocketAction.java 1.00 20200106
*/
package com.dph.websocket;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpSession;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.dph.websocket.config.HttpSessionConfigurator;
import com.dph.websocket.context.HttpSessionContext;
import com.dph.websocket.service.WebSocketService;
/**
* 20200106:Add by Dely<br/>
* WebSocketAction
* @author Dely
* @date 2020年1月6日 add
* @version 1.0
*/
@Component
@ServerEndpoint(value="/ws/websocket.action", configurator=HttpSessionConfigurator.class)
public class WebSocketAction {
private Session session;
private HttpSession httpSession;
private final transient Log log = LogFactory.getLog(getClass());
@Autowired
private WebSocketService webSocketService;
/**
* 20200106:Add by Dely<br/>
* Function: 打开连接时调用
* @date 2020年1月6日 add
* @author Dely
* @param session
*/
@OnOpen
public void open(Session session) {
setSession(session);
// 从session中获取请求并设置http session
Map<String, Object> userProperties = session.getUserProperties();
HttpSession httpSession = HttpSessionContext.getInstance().get((String)userProperties.get(HttpSessionContext.class.getName()));
if(null == httpSession) {
final HandshakeRequest request = (HandshakeRequest) userProperties.get(HandshakeRequest.class.getName());
httpSession = (HttpSession)request.getHttpSession();
}
// 得到http session后,移除请求,以免占用太多资源
userProperties.remove(HttpSessionContext.class.getName());
userProperties.remove(HandshakeRequest.class.getName());
setHttpSession(httpSession);
StringBuilder sb = new StringBuilder(80);
sb.append("OPEN: HTTP SESSION:")
.append(getHttpSessionIdStr())
.append(", WEBSOCKET SESSION:")
.append(getSession().getId());
log.debug(sb);
}
/**
* 20200106:Add by Dely<br/>
* Function: 发送信息时调用
* @date 2020年1月6日 add
* @author Dely
* @param message
* @throws IOException
*/
@OnMessage
public void message(String message) throws IOException {
StringBuilder sb = new StringBuilder(80);
sb.append("RECEIVE: HTTP SESSION:")
.append(getHttpSessionIdStr())
.append(", WEBSOCKET SESSION:")
.append(getSession().getId())
.append(", MESSAGE:")
.append(message);
log.debug(sb);
final String text = webSocketService.test();
String rtnText = text;
sb.delete(0, sb.length());
sb.append("SEND: HTTP SESSION:")
.append(getHttpSessionIdStr())
.append(", WEBSOCKET SESSION:")
.append(getSession().getId())
.append(", MESSAGE:")
.append(message);
log.debug(sb);
getSession().getBasicRemote().sendText(rtnText);
}
/**
* 20200106:Add by Dely<br/>
* Function: 关闭连接时调用
* @date 2020年1月6日 add
* @author Dely
*/
@OnClose
public void close() {
//
StringBuilder sb = new StringBuilder(80);
sb.append("CLOSE: HTTP SESSION:")
.append(getHttpSessionIdStr())
.append(", WEBSOCKET SESSION:")
.append(getSession().getId());
log.debug(sb);
}
/**
* 20200106:Add by Dely<br/>
* Function: 出错时调用
* @date 2020年1月6日 add
* @author Dely
* @param t
* @throws Throwable
*/
@OnError
public void error(Throwable t) throws Throwable {
//
StringBuilder sb = new StringBuilder(80);
sb.append("ERROR: HTTP SESSION:")
.append(getHttpSessionIdStr())
.append(", WEBSOCKET SESSION:")
.append(getSession().getId());
log.error(sb, t);
}
/**
* 20200106:Add by Dely<br/>
* Function: 获取HttpSession的id
* @date 2020年1月6日 add
* @author Dely
* @return
*/
public String getHttpSessionIdStr() {
final HttpSession s = getHttpSession();
return null == getHttpSession() ? "null" : getHttpSession().getId();
}
/**
* @return the session
*/
public Session getSession() {
return session;
}
/**
* @param session the session to set
*/
public void setSession(Session session) {
this.session = session;
}
/**
* @return the httpSession
*/
public HttpSession getHttpSession() {
return httpSession;
}
/**
* @param httpSession the httpSession to set
*/
public void setHttpSession(HttpSession httpSession) {
this.httpSession = httpSession;
}
}
```
5. 这部分就是正常的Service注解了
```
/**
* WebSocketService.java 1.00 20200106
* @author Dely
*/
package com.dph.websocket.service;
import org.springframework.stereotype.Service;
/**
* 20200106:Add by Dely<br/>
* WebSocketService
* @author Dely
* @date 2020年1月6日 add
* @version 1.0
*/
@Service
public class WebSocketService {
public String test() {
return "OK";
}
}
```
至此,即可用Spring注解注入到WebSocket中
现在发现这样很不好记录,还是换个平台记录吧
评论