赞
踩
此文不仅仅局限于spring boot,普通的spring工程,甚至是servlet工程,都是一样的,只不过配置一些监听器的方法不同而已。
本文经过作者实践,确认完美运行。
websocket本身是servlet容器所提供的服务,所以需要在web容器中运行,像我们所使用的tomcat,当然,spring boot中已经内嵌了tomcat。
websocket遵循了javaee规范,所以需要引入javaee的包
- <dependency>
- <groupId>javax</groupId>
- <artifactId>javaee-api</artifactId>
- <version>7.0</version>
- <scope>provided</scope>
- </dependency>
当然,其实tomcat中已经自带了这个包。
如果是在spring boot中,还需要加入websocket的starter
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- <version>1.4.0.RELEASE</version>
- </dependency>
如果不是spring boot项目,那就不需要进行这样的配置,因为如果在tomcat中运行的话,tomcat会扫描带有@ServerEndpoint的注解成为websocket,而spring boot项目中需要由这个bean来提供注册管理。
- @Configuration
- public class WebSocketConfig {
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
-
- }
使用websocket的核心,就是一系列的websocket注解,@ServerEndpoint是注册在类上面开启。
@ServerEndpoint(value = "/websocket") @Component public class MyWebSocket { //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接成功*/ @OnOpen public void onOpen(Session session) { this.session = session; } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { } /** * 收到消息 * * @param message */ @OnMessage public void onMessage(String message, Session session) { System.out.println("来自浏览器的消息:" + message); //群发消息 for (MyWebSocket item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 发生错误时调用 */ @OnError public void onError(Session session, Throwable error) { System.out.println("发生错误"); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message);//同步 //this.session.getAsyncRemote().sendText(message);//异步 } }
其实我也感觉很奇怪,为什么不使用接口来规范。即使是因为@ServerEndpoint注解中其它属性中可以定义出一些额外的参数,但相信也是可以抽象出来的,不过想必javaee这样做,应该是有它的用意吧。
浏览器端的代码需要浏览器支持websocket,当然,也有socket.js可以支持到ie7,但是这个我没用过。毕竟ie基本上没人用的,市面上的浏览器基本上全部都支持websocket。
- <!DOCTYPE HTML>
- <html>
- <head>
-
- </head>
-
- <body>
-
- </body>
-
- <script type="text/javascript">
- var websocket = null;
-
- //判断当前浏览器是否支持WebSocket
- if('WebSocket' in window){
- websocket = new WebSocket("ws://localhost:9999/websocket");
- }
- else{
- alert('不支持websocket')
- }
-
- //连接发生错误
- websocket.onerror = function(){
-
- };
-
- //连接成功
- websocket.onopen = function(event){
-
- }
-
- //接收到消息
- websocket.onmessage = function(event){
- var msg = event.data;
- alert("收到消息:" + msg);
- }
-
- //连接关闭
- websocket.onclose = function(){
-
- }
-
- //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
- window.onbeforeunload = function(){
- websocket.close();
- }
-
-
-
-
-
- //发送消息
- function send(message){
- websocket.send(message);
- }
- </script>
- </html>
如此就连接成功了。
获取HttpSession是一个很有必要讨论的问题,因为java后台需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权之类的,但是由于websocket的协议与Http协议是不同的,所以造成了无法直接拿到session。但是问题总是要解决的,不然这个websocket协议所用的场景也就没了。
我们先来看一下@ServerEndpoint注解的源码
package javax.websocket.server; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.websocket.Decoder; import javax.websocket.Encoder; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ServerEndpoint { /** * URI or URI-template that the annotated class should be mapped to. * @return The URI or URI-template that the annotated class should be mapped * to. */ String value(); String[] subprotocols() default {}; Class<? extends Decoder>[] decoders() default {}; Class<? extends Encoder>[] encoders() default {}; public Class<? extends ServerEndpointConfig.Configurator> configurator() default ServerEndpointConfig.Configurator.class; }
我们看到最后的一个方法,也就是加粗的方法。可以看到,它要求返回一个ServerEndpointConfig.Configurator的子类,我们写一个类去继承它。
按 Ctrl+C 复制代码
按 Ctrl+C 复制代码
当我们覆盖modifyHandshake方法时,可以看到三个参数,其中后面两个参数让我们感觉有点见过的感觉,我们查看一HandshakeRequest的源码
- package javax.websocket.server;
-
- import java.net.URI;
- import java.security.Principal;
- import java.util.List;
- import java.util.Map;
-
- /**
- * Represents the HTTP request that asked to be upgraded to WebSocket.
- */
- public interface HandshakeRequest {
-
- static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
- static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
- static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
- static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";
-
- Map<String,List<String>> getHeaders();
-
- Principal getUserPrincipal();
-
- URI getRequestURI();
-
- boolean isUserInRole(String role);
-
- /**
- * Get the HTTP Session object associated with this request. Object is used
- * to avoid a direct dependency on the Servlet API.
- * @return The javax.servlet.http.HttpSession object associated with this
- * request, if any.
- */
- Object getHttpSession();
-
- Map<String, List<String>> getParameterMap();
-
- String getQueryString();
- }
我们发现它是一个接口,接口中规范了这样的一个方法
- /**
- * Get the HTTP Session object associated with this request. Object is used
- * to avoid a direct dependency on the Servlet API.
- * @return The javax.servlet.http.HttpSession object associated with this
- * request, if any.
- */
- Object getHttpSession();
上面有相应的注释,说明可以从Servlet API中获取到相应的HttpSession。
当我们发现这个方法的时候,其实已经松了一口气了。
那么我们就可以补全未完成的代码
import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig.Configurator; /** * 从websocket中获取用户session * * */ public class HttpSessionConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession) request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(), httpSession); } }
其实通过上面的源码分析,你们应该知道了HttpSession的获取。但是下面又多了一行代码
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
这行代码又是什么意思呢?
我们看一下ServerEnpointConfig的声明
public interface ServerEndpointConfig extends EndpointConfig
我们发现这个接口继承了EndpointConfig的接口,好,我们看一下EndpointConfig的源码:
- package javax.websocket;
-
- import java.util.List;
- import java.util.Map;
-
- public interface EndpointConfig {
-
- List<Class<? extends Encoder>> getEncoders();
-
- List<Class<? extends Decoder>> getDecoders();
-
- Map<String,Object> getUserProperties();
- }
我们发现了这样的一个方法定义
Map<String,Object> getUserProperties();
可以看到,它是一个map,从方法名也可以理解到,它是用户的一些属性的存储,那既然它提供了get方法,那么也就意味着我们可以拿到这个map,并且对这里面的值进行操作,
所以就有了上面的
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
那么到此,获取HttpSession的源码分析,就完成了。
我们之前有说过,由于HTTP协议与websocket协议的不同,导致没法直接从websocket中获取协议,然后在3.1中我们已经写了获取HttpSession的代码,但是如果真的放出去执行,那么会报空指值异常,因为这个HttpSession并没有设置进去。
好,这一步,我们来设置HttpSession。这时候我们需要写一个监听器。
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @Component public class RequestListener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) { //将所有request请求都携带上httpSession ((HttpServletRequest) sre.getServletRequest()).getSession(); } public RequestListener() { } public void requestDestroyed(ServletRequestEvent arg0) { } }
然后我们需要把这个类注册为监听器,如果是普通的Spring工程,或者是servlet工程,那么要么在web.xml中配置,要么使用@WebListener注解。
因为本文是以Spring boot工程来演示,所以这里只写Spring boot配置Listener的代码,其它的配置方式,请自行百度。
这是使用@Bean注解的方式
- @Autowird
- private RequestListener requestListener;
-
-
- @Bean
- public ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean() {
- ServletListenerRegistrationBean<RequestListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
- servletListenerRegistrationBean.setListener(requestListener);
- return servletListenerRegistrationBean;
- }
或者也可以使用@WebListener注解
然后使用@ServletComponentScan注解,配置在启动方法上面。
然后刚才我们通过源码分析,是知道@ServerEndpoint注解中是有一个参数可以配置我们刚才继承的类。好的,我们现在进行配置。
@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)
接下来就可以在@OnOpen注解中所修饰的方法中,拿到EndpointConfig对象,并且通过这个对象,拿到之前我们设置进去的map
@OnOpen public void onOpen(Session session,EndpointConfig config){ HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); User user = (User)httpSession.getAttribute(SessionName.USER); if(user != null){ this.session = session; this.httpSession = httpSession; }else{ //用户未登陆 try { session.close(); } catch (IOException e) { e.printStackTrace(); } } }
这下我们就从java的webscoket中拿到了用户的session。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。