Java for Web学习笔记(九一):消息和集群(6)利用websocket实现订阅和发布(下)

ClusterEventMulticaster:自定义的ApplicationEventMulticaster

设置为root上下文的bean

在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;
}

ClusterEventMulticaster代码

public class ClusterEventMulticaster extends SimpleApplicationEventMulticaster{
    private static final Logger log = LogManager.getLogger();
    private final Set<ClusterMessagingEndpoint> 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);
        }
    }
}

ClusterMessagingEndpoint:websocket的endpoint

//【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<String, String> 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<ClusterEvent>,Decoder.BinaryStream<ClusterEvent> {
        @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相关文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值