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

来源:互联网 发布:北京中轴线一日游 知乎 编辑:程序博客网 时间:2024/06/01 10:43

ClusterEventMulticaster:自定义的ApplicationEventMulticaster

设置为root上下文的bean

在root上下文的配置文件中:

@Beanpublic 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 0
原创粉丝点击