Using TextWebSocketHandler as @Aspect: after triggering of the interceptor WebSocketSession instanse becomes null

1k Views Asked by At

I have a class which extends TextWebSocketHandler and it's main purpose is to send messages to client after a certain event with WebSocketSession. To implement this I've decided to use the method interception provided by Spring AOP.

The issue is when the interceptor is called the incapsulated instance of WebSocketSession becomes null even though before it was instantiated in method afterConnectionEstablished() (which can be seen with debugger). Interesting thing is that in every other method of the class the session instance can be accessed.

Here is the code of the class:

@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);
  }
}

And the context configuration file:

<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 referenced in @Before is actually the method in one of the application's REST controllers but I don't think that matters much here.

Unfortunately I'm not familiar with aspects that much. To begin with, I don't even know if it's possible to combine socket handler with @Aspect. And if it's possible after all what is the reason why WebSocketSession becomes null an how can I possibly avoid it? Any help is appreciated.

1

There are 1 best solutions below

0
On BEST ANSWER

So the actual reason of that behaviour was the existense of many instances of SystemStateWS at the moment of triggering of the interceptor. And the interceptor just triggered exactly in the instance where method afterConnectionEstablished() was never called and so the reference of WebSocketSession continued to stay null here.

So I've decided to register each instance of my web socket class whenever method afterConnectionEstablished() is called in one special bean and then made standalone class for the interceptor. When it is called now, it just calls the necessary method in all registered web socket instances. This code did the trick for me:

Registration of the socket class in buffer:

  @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());
  }

}