最近项目上要做扫码登录,所以研究了一下Spring WebSocket。网上找了很多资料 springmvc(18)使用WebSocket 和 STOMP 实现消息功能、spring websocket + stomp 实现广播通信和一对一通信,要么就是不是自己想要的,要么就是只有中间一部分。所以特别写了这篇文章,一方面怕自己遗忘,另一方面是希望可以给大家一些参考。
先放代码,在文章的最后我会把项目地址给大家。这个项目是可以运行的,直接导入Idea就可以了。
pom.xml文件:(这里只有最基本的包,Spring必须得是4.0+)
4.2.8.RELEASE
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-messaging
${spring.version}
org.springframework
spring-websocket
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-web
${spring.version}
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-beans
${spring.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-context-support
${spring.version}
com.fasterxml.jackson.core
jackson-databind
2.5.3
runtime
web.xml:(servlet和所有的filter都要加
index.jsp
contextConfigLocation
classpath:config/spring/*.xml
org.springframework.web.context.ContextLoaderListener
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:config/spring/*.xml
1
true
dispatcherServlet
/
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
true
encodingFilter
/*
dispatcher.xml:(SpringMVC配置文件)
WebSocketConfig类:(Spring WebSocket的配置文件,这里采用的是注解的方式)
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* Created by haoyuyang on 2016/11/25.
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
/**
* 将"/hello"路径注册为STOMP端点,这个路径与发送和接收消息的目的路径有所不同,这是一个端点,客户端在订阅或发布消息到目的地址前,要连接该端点,
* 即用户发送请求url="/applicationName/hello"与STOMP server进行连接。之后再转发到订阅url;
* PS:端点的作用——客户端在订阅或发布消息到目的地址前,要连接该端点。
* @param stompEndpointRegistry
*/
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
//在网页上可以通过"/applicationName/hello"来和服务器的WebSocket连接
stompEndpointRegistry.addEndpoint("/hello").setAllowedOrigins("*").withSockJS();
}
/**
* 配置了一个简单的消息代理,如果不重载,默认情况下回自动配置一个简单的内存消息代理,用来处理以"/topic"为前缀的消息。这里重载configureMessageBroker()方法,
* 消息代理将会处理前缀为"/topic"和"/queue"的消息。
* @param registry
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//应用程序以/app为前缀,代理目的地以/topic、/user为前缀
registry.enableSimpleBroker("/topic", "/user");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
}
PS:
registry.enableSimpleBroker("/topic", "/user");这句话表示在topic和user这两个域上可以向客户端发消息。
registry.setUserDestinationPrefix("/user");这句话表示给指定用户发送一对一的主题前缀是"/user"。
registry.setApplicationDestinationPrefixes("/app");这句话表示客户单向服务器端发送时的主题上面需要加"/app"作为前缀。
stompEndpointRegistry.addEndpoint("/hello").setAllowedOrigins("*").withSokJS();这个和客户端创建连接时的url有关,其中setAllowedOrigins()方法表示允许连接的域名,withSockJS()方法表示支持以SockJS方式连接服务器。
接下来是测试类GreetingController:
import com.hyy.model.Greeting;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* Created by haoyuyang on 2016/11/25.
*/
@RestController
public class GreetingController {
@Autowired
private SimpMessageSendingOperations simpMessageSendingOperations;
/**
* 表示服务端可以接收客户端通过主题“/app/hello”发送过来的消息,客户端需要在主题"/topic/hello"上监听并接收服务端发回的消息
* @param topic
* @param headers
*/
@MessageMapping("/hello") //"/hello"为WebSocketConfig类中registerStompEndpoints()方法配置的
@SendTo("/topic/greetings")
public void greeting(@Header("atytopic") String topic, @Headers Map headers) {
System.out.println("connected successfully....");
System.out.println(topic);
System.out.println(headers);
}
/**
* 这里用的是@SendToUser,这就是发送给单一客户端的标志。本例中,
* 客户端接收一对一消息的主题应该是“/user/” + 用户Id + “/message” ,这里的用户id可以是一个普通的字符串,只要每个用户端都使用自己的id并且服务端知道每个用户的id就行。
* @return
*/
@MessageMapping("/message")
@SendToUser("/message")
public Greeting handleSubscribe() {
System.out.println("this is the @SubscribeMapping('/marco')");
return new Greeting("I am a msg from SubscribeMapping('/macro').");
}
/**
* 测试对指定用户发送消息方法
* @return
*/
@RequestMapping(path = "/send", method = RequestMethod.GET)
public Greeting send() {
simpMessageSendingOperations.convertAndSendToUser("1", "/message", new Greeting("I am a msg from SubscribeMapping('/macro')."));
return new Greeting("I am a msg from SubscribeMapping('/macro').");
}
}
PS:
这个类里面注入了SimpMessagingTemplete对象,后面动态发送消息时需要这个对象。
public class Greeting {
private String content;
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
测试页面websocket.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Hello WebSocket
PS:
stompClient.subscribe('/topic/greetings', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});该方法是接收广播消息。
stompClient.subscribe('/user/' + userid + '/message',function(greeting){
alert(JSON.parse(greeting.body).content);
showGreeting(JSON.parse(greeting.body).content);
});该方法表示接收一对一消息,其主题是"/user/"+userId+"/message",不同客户端具有不同的id。如果两个或多个客户端具有相同的id,那么服务器端给该userId发送消息时,这些客户端都可以收到。
如果项目中配置了拦截器,浏览器console标签中会报如下错误:
sockjs.min.js:2 GET http://localhost:8200/s3captrue/endpoint/info?t=1480493527907
XMLHttpRequest cannot load http://localhost:8200/s3captrue/endpoint/info?t=1480493527907. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 500.
JAVA的控制台会输出如下错误:
java.lang.ClassCastException: org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod
则需要过滤掉对websoket的拦截:
PS:proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
本例的代码:https://github.com/haoyuyang/SpringWebSocket-SockJS-STOMP