使用TextWebSocketHandler作为@Aspect:触发拦截器后,WebSocketSession instanse变为null

时间:2016-12-21 14:43:05

标签: java spring aspectj spring-aop spring-websocket

我有一个扩展TextWebSocketHandler的类,其主要目的是在WebSocketSession的某个事件之后向客户发送消息。为了实现这一点,我决定使用Spring AOP提供的方法拦截。

问题是当拦截器被调用时WebSocketSession的封装实例变为null,即使它在方法afterConnectionEstablished()中实例化之前(可以通过调试器看到)。有趣的是,在类的每个其他方法中都可以访问会话实例。

以下是该类的代码:

@Aspect
public class SystemStateWS extends TextWebSocketHandler {

  private static final Logger LOGGER = LoggerFactory.getLogger(SystemStateWS.class);

  private WebSocketSession session;

  public SystemStateWS() {

  }

  @Override
  public synchronized void afterConnectionEstablished(WebSocketSession session) throws Exception {
    this.session = session;
  }

  @Override
  protected synchronized void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    System.out.println(message.getPayload());
  }

  @Before("execution(* org.company.MyRestService.processTablesClearQuery(..))")
  public void onConfigurationStart(JoinPoint joinPoint) {
    if (session.isOpen()) { // session is null here
      try {
        session.sendMessage(new TextMessage("config started"));
        LOGGER.debug("Configuration status message was sent to client");

      } catch (IOException e) {
        LOGGER.warn("Error during the passing of the message to client");
      }
    } else {
      LOGGER.warn("Unable to send message to client: session is closed");
    }

  }

  @Override
  public synchronized void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    LOGGER.debug("Socket is closing");
  }


  @Override
  public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    super.handleTransportError(session, exception);
    LOGGER.warn("Transport error", exception);
  }
}

上下文配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd      
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop         
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <context:component-scan base-package="org.company" />

    <aop:aspectj-autoproxy/>
    ...

    <websocket:handlers>
        <websocket:mapping path="/ws/sysState" handler="sysStateHandler" />
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="335544"/>
        <property name="maxBinaryMessageBufferSize" value="335544"/>
    </bean>

    <bean id="handshakeHandler" class="org.springframework.web.socket.server.support.DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy">
    </bean>

    <bean id="sysStateHandler" class="org.springframework.web.socket.handler.PerConnectionWebSocketHandler">
        <constructor-arg type="java.lang.Class" value="org.company.SystemStateWS"/>
    </bean>

    <bean id="systemStateWS" class="org.company.SystemStateWS"/>

</beans>
MyRestService.processTablesClearQuery中引用的

@Before实际上是应用程序的一个REST控制器中的方法,但我认为这里并不重要。

不幸的是,我对这些方面并不熟悉。首先,我甚至不知道是否可以将套接字处理程序与@Aspect组合在一起。如果可能的话WebSocketSession成为null的原因是什么,我怎么可能避免它?任何帮助表示赞赏。

1 个答案:

答案 0 :(得分:0)

所以这种行为的实际原因是在触发拦截器的那一刻,许多SystemStateWS实例的存在。拦截器刚刚在从未调用方法afterConnectionEstablished()的实例中触发,因此WebSocketSession的引用继续保留null

因此,每当在一个特殊bean中调用方法afterConnectionEstablished(),然后为拦截器创建独立类时,我就决定注册我的Web套接字类的每个实例。当它现在被调用时,它只是在所有已注册的Web套接字实例中调用必要的方法。这段代码对我有用:

在缓冲区中注册套接字类:

  @Autowired
  private SystemStateWebSocketBuffer buffer;
  //...
  @Override
  public synchronized void afterConnectionEstablished(WebSocketSession session) 
                           throws Exception {
    this.session = session;
    buffer.add(this);
  }

<强> SystemStateWebSocketBuffer.java

@Component
public class SystemStateWebSocketBuffer {
  private List<SystemStateWS> systemStateSockets = new ArrayList<>();

  public void add(SystemStateWS systemStateWS) {
    this.systemStateSockets.add(systemStateWS);
  }

  public List<SystemStateWS> getSystemStateSockets() {
    return systemStateSockets;
  }

  public void setSystemStateSockets(List<SystemStateWS> systemStateSockets) {
    this.systemStateSockets = systemStateSockets;
  } 

}

<强> SystemStateRestInterceptor.java

@Aspect
public class SystemStateRestInterceptor {
  @Autowired
  private SystemStateWebSocketBuffer systemStateWebSocketBuffer;

  @Before("execution(* org.company.MyRestService.processTablesClearQuery(..))")
  public void interceptConfigStartEvent() {
    systemStateWebSocketBuffer.getSystemStateSockets()
                              .forEach(socket -> socket.onConfigurationStart());
  }

}