将 TextWebSocketHandler 用作 @Aspect:触发拦截器后 WebSocketSession 实例变为 null

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

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

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

下面是 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);
  }
}

和上下文配置文件:

<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 的原因是什么?我怎样才能避免呢?感谢您的帮助。

所以该行为的实际原因是在触发拦截器时存在许多 SystemStateWS 实例。并且拦截器恰好在从未调用方法 afterConnectionEstablished() 的实例中触发,因此 WebSocketSession 的引用继续留在此处 null

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

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

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

}