dph5199278

解决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中

现在发现这样很不好记录,还是换个平台记录吧

评论