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

在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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值