在root上下文的配置文件中:
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(){
SimpleApplicationEventMulticaster eventMulticaster = new ClusterEventMulticaster();
//ClusterManager也是在监听事件,如果我们没有再次设置后台运行setTaskExecutor(),那么ClusterManager中的onApplicationEvent()就必须带上@Async的标记,否则这里会花费1分钟的时间,在此期间,由于无法往下继续进行web上下文的初始化(需先完成onApplicationEvent()),即/ping无法正常回应,最终导致失败。安全起见,我们仍可以在ClusterManager的onApplicationEvent()上加上@Async,大不了属于线程中的线程。
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
public class ClusterEventMulticaster extends SimpleApplicationEventMulticaster{
private static final Logger log = LogManager.getLogger();
private final Set endpoints = new HashSet<>(); //管理和维护各个websocket连接
@Inject ApplicationContext context; //作为在Root上下文中设置为bean,因此这里将获得的是root上下文
//【1】自定义消息发布,如果属于ClusterEvent事件,将通过websocket发布给集群(当然排除掉接收的集群事件)
@Override
public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
try{
super.multicastEvent(event, eventType);
}finally{
try{
if(event instanceof ClusterEvent && !((ClusterEvent)event).isRebroadcasted())
publishClusteredEvent((ClusterEvent)event);
}catch(Exception e){
log.error("Failed to broadcast distributable event to cluster.",e);
}
}
}
//【2】集群事件收发的相关方法
//【2.1】通过websocket向集群广播事件
private void publishClusteredEvent(ClusterEvent event){
synchronized(endpoints){
for(ClusterMessagingEndpoint endpoint : this.endpoints){
endpoint.send(event);
}
}
}
//【2.2】从websocket中接收到事件,需要向本地发布,以便触发响应的ApplicationEventListener。同时设置了消息中的rebroadcasted标记,表明无需向集群广播词消息
protected final void handleReceivedClusteredEvent(ClusterEvent event){
event.setRebroadcasted();
this.multicastEvent(event,null); //相当于在本地发布
}
//【3】管理socket连接(ClusterMessagingEndpoint)
//【3.1】注册websocket连接
protected void registerEndpoint(ClusterMessagingEndpoint endpoint){
synchronized (endpoints) {
this.endpoints.add(endpoint);
}
}
//【3.2】注销websocket连接
protected void deregisterEndpoint(ClusterMessagingEndpoint endpoint){
synchronized(endpoints){
this.endpoints.remove(endpoint);
}
}
//【3.3】程序退出时,关闭所有的websocket连接
@PreDestroy
public void shutdown(){
synchronized(this.endpoints){
for(ClusterMessagingEndpoint endpoint : this.endpoints){
endpoint.close();
}
}
}
//【3.4】此处更合适的名字是createNode,根据监听的到websocket的url,创建连接(创建ClusterMessagingEndpoint对象),需要注意的WebSocketEndpoint不属于Spring框架,要手动将其纳入,否则无法在WebSocketEndpoint中支持@Inject。这在之前学些过,再次重温。
protected void registerNode(String endpoint){
log.info("Connecting to cluster node {}.", endpoint);
/* A WebSocketContainer is an implementation provided object that provides applications a view
* on the container running it. The WebSocketContainer container various configuration parameters
* that control default session and buffer properties of the endpoints it contains. It also allows
* the developer to deploy websocket client endpoints by initiating a web socket handshake from
* the provided endpoint to a supplied URI where the peer endpoint is presumed to reside.
*
* A WebSocketContainer may be accessed by concurrent threads, so implementations must ensure the
* integrity of its mutable attributes in such circumstances.
*
* URI uri = new URI("ws","localhost:8080",path,null,null);
* Session session = ContainerProvider.getWebSocketContainer().connectToServer(this, uri);
* */
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
/* ClusterMessagingEndpoint是websocket的cilent+server,websocket不是spring framework。如果要向bean那样可以自动注入等,我们需要使用org.springframework.beans.factory.annotation.Configurable标注对于非Spring管理的bean。*/
ClusterMessagingEndpoint bean = this.context.getAutowireCapableBeanFactory()
.createBean(ClusterMessagingEndpoint.class);
try {
log.info("Connect to server {}", endpoint);
container.connectToServer(bean, new URI(endpoint));
} catch (DeploymentException | IOException | URISyntaxException e) {
log.error("Failed to connect to cluster node {}.", endpoint, e);
}
}
}
//【1】我们关心的是从websocket连接中收到的集群的event,对于websocket的server和client放在一起处理。将事件对象序列化后,直接在websocket的连接中传递,因此codec就是实现序列化和反序列化,由内部静态类Codec来实现
@ServerEndpoint(value = "/services/Messaging/{securityCode}",
encoders = { ClusterMessagingEndpoint.Codec.class },
decoders = { ClusterMessagingEndpoint.Codec.class },
configurator = SpringConfigurator.class)
@ClientEndpoint(
encoders = { ClusterMessagingEndpoint.Codec.class },
decoders = { ClusterMessagingEndpoint.Codec.class })
public class ClusterMessagingEndpoint {
private static final Logger log = LogManager.getLogger();
private Session session;
//支持@Inject,对于server,设置configurator为SpringConfigurator.class,每一个新的连接都会创建@ServiceEndpoint的bean。对于client需要作为Spring的bean创建,见之前在ClusterEventMulticaster中的代码。
@Inject ClusterEventMulticaster multicaster;
@OnOpen
public void open(Session session){
Map parameters = session.getPathParameters();
if(parameters.containsKey("securityCode") && !"a83teo83hou9883hha9".equals(parameters.get("securityCode"))){
try {
log.error("Received connection with illegal code {}.", parameters.get("securityCode"));
session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Illegal Code"));
} catch (IOException e) {
log.warn("Failed to close illegal connection.", e);
}
}else{
log.info("Successful connection onOpen.");
this.session = session;
this.multicaster.registerEndpoint(this); //websocket连接成功建立
}
}
@OnMessage
public void receive(ClusterEvent message){
this.multicaster.handleReceivedClusteredEvent(message); //收到集群中的消息
}
@OnClose
public void close(){
log.info("Cluster node connection closed.");
this.multicaster.deregisterEndpoint(this); //对方关闭连接
if(this.session.isOpen()){
try {
this.session.close();
} catch (IOException e) {
log.warn("Error while closing cluster node connection.", e);
}
}
}
public void send(ClusterEvent message){
try {
session.getBasicRemote().sendObject(message); //通过websocket连接发布事件(消息)
} catch (IOException | EncodeException e) {
log.error("Failed to send message to adjacent node.", e);
}
}
//【2】将事件对象序列化后,直接在websocket的连接中传递,因此codec就是实现序列化和反序列化,由内部静态类Codec来实现
public static class Codec implements Encoder.BinaryStream,Decoder.BinaryStream {
@Override
public void init(EndpointConfig config) {
}
@Override
public void destroy() {
}
@Override
public ClusterEvent decode(InputStream is) throws DecodeException, IOException {
try(ObjectInputStream ois = new ObjectInputStream(is);){
try {
return (ClusterEvent)ois.readObject();
} catch (ClassNotFoundException e) {
throw new DecodeException((String)null, "Failed to decode.", e);
}
}
}
@Override
public void encode(ClusterEvent object, OutputStream os) throws EncodeException, IOException {
try(ObjectOutputStream oos = new ObjectOutputStream(os);){
oos.writeObject(object);
}
}
}
}
相关链接: 我的Professional Java for Web Applications相关文章