将 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());
}
}
我有一个扩展 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());
}
}