You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JSR356 WebSocket中ServerEndpoint的PostConstruct为何被调用两次?

疑问:JSR356 WebSocket的ServerEndpoint中PostConstruct方法为何被调用两次?

我在Web应用中使用JSR356 WebSocket时,发现@ServerEndpoint标注的类里的@PostConstruct方法被调用了两次,这让我很困惑。


服务器日志(WildFly的server.log)

PushMessageEndpoint: -> onPostConstruct(): at.luxbau.mis2.web.controller.prozess.PushMessageEndpoint@653dde97[session=<null>,sessionId=<null>]
PushMessageEndpoint: <- onPostConstruct()
PushMessageEndpoint: -> onPostConstruct(): at.luxbau.mis2.web.controller.prozess.PushMessageEndpoint@653dde97[session=<null>,sessionId=<null>]
PushMessageEndpoint: <- onPostConstruct()
PushMessageEndpoint: -> onOpen(): io.undertow.websockets.jsr.UndertowSession@43a032ff, M3OLaoTSRxM7EZWLH1LSzp7hFQBe24QvwLG7jDqr
PushMessageEndpoint: endpoints.size=1
PushMessageEndpoint: endpoint: at.luxbau.mis2.web.controller.prozess.PushMessageEndpoint@653dde97[session=io.undertow.websockets.jsr.UndertowSession@43a032ff,sessionId=M3OLaoTSRxM7EZWLH1LSzp7hFQBe24QvwLG7jDqr]
PushMessageEndpoint: <- onOpen()

服务端端点代码(PushMessageEndpoint.java)

@ServerEndpoint(value = "/push/{sessionId}")
public class PushMessageEndpoint {

    private static final ILogger logger = LogFactory.getLogger(PushMessageEndpoint.class);

    private static final Set<PushMessageEndpoint> ENDPOINTS = new CopyOnWriteArraySet<>();

    private Session session;
    private String sessionId;

    @PostConstruct
    void onPostConstruct() {
        logger.entry(this);
        logger.exit();
    }
    
    @OnOpen
    public void onOpen(Session session, @PathParam("sessionId") String sessionId) {
        logger.entry(session, sessionId);

        this.session = session;
        this.sessionId = sessionId;

        ENDPOINTS.add(this);
        logEndpoints();

        logger.exit();
    }
    
    private void logEndpoints() {
        logger.debugf("endpoints.size=%d", ENDPOINTS.size());
        ENDPOINTS.forEach(ep -> logger.debugf("endpoint: %s", ep));
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("session", session)
                .append("sessionId", sessionId)
                .toString();
    }
}

页面模板(template.xhtml)

每个页面都会引入这个模板:

<!-- 每个页面都通过SessionId打开一个WebSocket -->
<h:outputScript>
    var sessionId = "${request.session.id}"
    openWebSocket(sessionId);
</h:outputScript>
<h:outputScript library="js" name="websocket.js"/>

WebSocket客户端代码(websocket.js)

function openWebSocket(sessionId) {
    if (window.WebSocket) {
        var wsUri = getRootUri() + "/" + sessionId;
        var ws = new WebSocket(wsUri);
        ws.onmessage = function (event) {
            var message = event.data;
            console.log(message);
            if (window["updateMessage"]) {
                // 调用RemoteCommand 'updateMessage'并传入消息作为参数
                window["updateMessage"]([{'name': 'message', 'value': message}]);
            }
            // 接收消息后关闭连接
            // ws.close();
        }
        ws.onopen = function (event) {
            console.log("open web socket : " + wsUri);
        }
        ws.onclose = function (event) {
            console.log("close web socket : " + wsUri
                + "  (" + event.code + ") " + event.reason + ", wasClean is " + event.wasClean);
        }
    } else {
        console.log("Browser does not support WebSockets!");
    }
}

问题分析与解决

从日志能看到,两次onPostConstruct调用的是同一个实例(@653dde97),说明不是创建了两个端点实例,而是同一个实例被两个容器重复初始化了:

  1. CDI容器初始化:如果应用启用了CDI(比如存在beans.xml或符合Java EE默认CDI启用规则),CDI会扫描到PushMessageEndpoint并将其视为CDI bean,调用@PostConstruct方法。
  2. WebSocket容器初始化:JSR356 WebSocket容器同样会初始化这个端点类,再次触发@PostConstruct方法。

解决方法

  • 方法一:阻止CDI扫描:在PushMessageEndpoint类上添加@Vetoed注解,禁止CDI容器将其作为bean管理,这样只有WebSocket容器会初始化它,@PostConstruct只会被调用一次:
    @ServerEndpoint(value = "/push/{sessionId}")
    @Vetoed
    public class PushMessageEndpoint {
        // ... 原有代码 ...
    }
    
  • 方法二:替换初始化逻辑:如果不需要CDI管理该端点,直接去掉@PostConstruct注解,将初始化逻辑移到@OnOpen方法中——@OnOpen是WebSocket规范定义的连接建立回调,更适合处理WebSocket相关的初始化操作。

另外,客户端日志仅显示一次连接建立,说明客户端没有重复发起请求,问题根源完全在服务端双容器初始化。


内容的提问来源于stack exchange,提问作者raho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:55:56