WebSocket 作用域
每个 WebSocket 会话都拥有一组属性(Attributes)。这些属性会作为标头附加到客户端发送的消息中,并可以在控制器方法中访问:
java
@Controller
public class MyController {
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
}使用 @WebSocketScope
你可以在 Spring 中声明一个具有 websocket 作用域的 Bean。这些 Bean 可以被注入到控制器或在 clientInboundChannel 上注册的任何拦截器中。
由于控制器通常是单例(Singleton),其寿命比 WebSocket 会话长,因此必须以代理模式使用 WebSocket 作用域的 Bean:
java
@Component
@WebSocketScope
public class MyBean {
@PostConstruct
public void init() {
// 在依赖注入后调用
}
@PreDestroy
public void destroy() {
// 在 WebSocket 会话结束时调用
}
}
@Controller
public class MyController {
private final MyBean myBean;
@Autowired
public MyController(MyBean myBean) {
this.myBean = myBean;
}
@MessageMapping("/action")
public void handle() {
// 此处的 myBean 实例属于当前 WebSocket 会话
}
}与任何自定义作用域一样,Spring 会在控制器第一次访问 myBean 时实例化它,并将其存储在会话属性中。直到会话结束前,后续的所有访问都会返回同一个实例。
补充教学
1. 作用域的物理存储
实际上,这些 Bean 实例是被存储在 WebSocket Session 的 attributes 映射表里的。这也意味着,如果你使用了外部消息代理(集群环境),这些 Bean 的数据默认是不能跨服务器共享的,除非你使用了 Spring Session 这种外部存储机制。
2. 生命周期回调的威力
@PostConstruct 和 @PreDestroy 非常强大。你可以用 @PreDestroy 在用户离开时自动清理资源,比如关闭打开的文件句柄、通知其他用户该玩家已下线等。
3. @WebSocketScope 还是 Map?
如果你只需要存几个简单的 key-value,直接用 headerAccessor.getSessionAttributes() 更轻量。如果你需要封装复杂的业务状态(如一个在线游戏的“当前玩家房间信息”对象),使用 @WebSocketScope 的 Bean 能让代码结构更清晰、更符合 Spring 的依赖注入哲学。