Java for Web学习笔记(七二):Service和Repository(7)在Spring框架中使用WebSocket

来源:互联网 发布:php while 死循环 编辑:程序博客网 时间:2024/06/05 08:52

在WebSocket中注入spring bean

  和Listener一样,websocket不属于spring framework,不是spring framework天然的bean,因此无法在里面直接注入spring的bean。

SpringCongifurator

  为了让websocket更好地在Spring framework中工作,WebSocket Endpoint的配置器将继承SpringCongifurator。我们需要在pom.xml中引入相关的jar包:

<dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-websocket</artifactId>     <version>${spring.framework.version}</version>     <scope>compile</scope></dependency>

  我们可以通过SpringCongifurator作为websocket endpoint的配置器

@ServerEndpoint(value = "/chat/{sessionId}",                configurator = SpringCongifurator.class)public class ChatEndpoint {    ... ...}

  如果需要自定义配置器,具体见下面代码:

@ServerEndpoint(value = "/chat/{sessionId}",                encoders = ChatMessageCodec.class,                decoders = ChatMessageCodec.class,                configurator = ChatEndpoint.EndpointConfigurator.class)public class ChatEndpoint {    @Inject SessionRegistryService sessionRegisterService;    ... ...    // SpringConfigurator是继承了ServerEndpointConfig.Configurator(参见 WebSocket(5):encoder,decoder和configurator#configurator)    // 本例,我们将httpSession存放在websocket.Session的user Properties中,因此采用自定义的方式。为了便于获取,我们提供了两个静态方法。当然我们可以使用 webSocketSession.getUserProperties().get(key) 来获取    // 通过SpringConfigurator,我们就可以在ChatEndpoint中使用Spring的注入,如上面的SessionRegistryService,但ChatEndpoint并不属于Spring framework,不能作为Bean被注入。如果我们想将ChatEndpoint作为一个Bean,可以在RootContext的配置中    // @Bean    // public ChatEndpoint chatEndpoint(){    //      return new ChatEndpoint();    // }    // 但是这样一来系统就只有一个ChatEndpoint管理所有的连接,而不是每个ChatEndpoint管理一条连接,处理起来要复杂很多。    public static class EndpointConfigurator extends SpringConfigurator{        private static final String HTTP_SESSION_KEY = "cn.wei.flowingflying.customer_support.http.session";        private static final String PRINCIPAL_KEY = "cn.wei.flowingflying.customer_support.user.principal";                @Override        public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {            super.modifyHandshake(config, request, response);            HttpSession session = (HttpSession)request.getHttpSession();            config.getUserProperties().put(HTTP_SESSION_KEY, session);            config.getUserProperties().put(PRINCIPAL_KEY, UserPrincipal.getPrincipal(session));                    }        private static HttpSession getExposedSession(Session session){            return (HttpSession) session.getUserProperties().get(HTTP_SESSION_KEY);        }        private static Principal getExposedPrincipal(Session session){            return (Principal)session.getUserProperties().get(PRINCIPAL_KEY);        }           }}

在websocket中使用Scheduler

  使用websocket,一般情况下,一个连接对应要给websocket实例,也就是我们不采用在root context中配置websocket为bean,这会导致所有的连接都对应到同一个websocket实例中。也就是websocket实例不作为singleton bean。而之前学习的@Scheduled必须加载singleton bean上,我们需要另辟方法。

  下面的例子我们在websocket中通过taskSechdule启动ping-pong机制,在chat结束时终止。在例子中我们接上一学习的例子,演示了在http session删除时,结束chat,重点看看如何使用consumer。

@ServerEndpoint(value = "/chat/{sessionId}",                encoders = ChatMessageCodec.class,                decoders = ChatMessageCodec.class,                configurator = ChatEndpoint.EndpointConfigurator.class)public class ChatEndpoint {    private static final byte[] pongData = "This is PONG country.".getBytes(StandardCharsets.UTF_8);    private ScheduledFuture<?> pingFuture;    @Inject SessionRegistryService sessionRegisterService;    //我们不能直接在sendPing()前加入@Scheduled,pring的@Schedule只支持singleton bean,而ChatEndpoint不是,我们没有采用在root Context中将其设置为bean,而是每条连接都有一个实例。因此,我们将直接使用TaskScheduler。    @Inject TaskScheduler taskScheduler;    // 在sessionRegisterService的登记和注销httpSession删除的的回调函数,如果我们不将其赋给一个对象,Method Reference中this::httpSessionRemoved每次的值并不一样,要确保注册和注销中是同一个回调函数,需要将其固化。    private final Consumer<HttpSession> callback = this::httpSessionRemoved;    /* 用于在注入后执行。对于支持注入的类都会支持@PostConstruct,不仅是spring,WebSocket的ServerEndpoint支持。在接收到websocket client的连接请求的时候,server endpoint对象被创建,然后执行@postConstruct */    @PostConstruct    public void initialize(){        this.sessionRegisterService.registerOnRemoveCallback(this.callback);        this.pingFuture = this.taskScheduler.scheduleWithFixedDelay(                               this::sendPing,                               new Date(System.currentTimeMillis() + 25_000L), // start time                               25_000L); //delay     }    // httpSession删除时的处理回调函数。    private void httpSessionRemoved(HttpSession httpSession){        if(httpSession == this.httpSession){            synchronized(this){                if(this.closed)                    return;                log.info("Chat session ended abruptly by {} logging out.", this.principal.getName());                this.close(ChatService.ReasonForLeaving.LOGGED_OUT, null);            }        }    }    //具体处理chat关闭    private void close(ChatService.ReasonForLeaving reason, String unexpected){         this.closed = true;        if(this.pingFuture.isCancelled())            this.pingFuture.cancel(true);        this.sessionRegisterService.deregisterOnRemoveCallback(this.callback);    }     private void sendPing(){//发送ping        ... ...        this.wsSession.getBasicRemote().sendPing(ByteBuffer.wrap(ChatEndpoint.pongData));    }     @OnMessage    public void onPong(PongMessage message){ //收到pong        ByteBuffer data = message.getApplicationData();        if(!Arrays.equals(ChatEndpoint.pongData, data.array()))             log.warn("Received pong message with incorrect payload.");        else             log.debug("Received good pong message.");   }    ... ...}

相关链接: 我的Professional Java for Web Applications相关文章

阅读全文
0 0