如何编写兼容Tomcat与WebLogic的Java WebSocket Servlet
好问题!这确实是企业级Java Web应用跨容器适配时的典型痛点——别担心,Java EE规范早就帮我们解决了这个问题:使用JSR 356(Java API for WebSocket)标准API,Tomcat 7.0.47+、WebLogic 12.1.3+都原生支持这套规范,完全可以写出一套代码兼容两个容器,不需要为每个容器单独实现。
下面我给你拆解具体实现步骤:
1. 依赖配置:只引入标准API,避免容器冲突
首先,确保你的项目只依赖JSR 356的API包(编译期用,运行时由容器提供实现),不要引入任何Tomcat/WebLogic特定的WebSocket实现包。以Maven为例:
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>
<scope>provided</scope>很关键,它告诉构建工具不要把这个包打包到WAR里,避免和容器自带的WebSocket实现类冲突。
2. 方式一:注解式端点(最简便,推荐)
JSR 356提供了@ServerEndpoint注解,直接标记一个普通Java类就能成为WebSocket端点,不需要继承任何容器特定的Servlet类。容器会自动识别并适配自身的WebSocket机制。
示例代码:
import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; @ServerEndpoint("/chat") public class ChatEndpoint { // 存储所有在线会话(线程安全) private static final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>(); @OnOpen public void onOpen(Session session) { sessions.add(session); System.out.println("新客户端连接:" + session.getId()); } @OnMessage public void onMessage(String message, Session session) throws IOException { // 广播消息给所有在线客户端 for (Session s : sessions) { if (s.isOpen()) { s.getBasicRemote().sendText("用户[" + session.getId() + "]:" + message); } } } @OnClose public void onClose(Session session) { sessions.remove(session); System.out.println("客户端断开连接:" + session.getId()); } @OnError public void onError(Session session, Throwable error) { System.err.println("WebSocket错误:" + error.getMessage()); } }
这个类在Tomcat和WebLogic上都能直接运行,客户端只需要通过ws://<host>:<port>/<context-path>/chat连接即可。
3. 方式二:编程式端点(适合动态注册场景)
如果需要动态注册WebSocket端点(比如根据配置决定是否开启),可以使用编程式方式,继承JSR 356的Endpoint类:
import javax.websocket.*; import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; public class ChatProgrammaticEndpoint extends Endpoint { private static final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>(); @Override public void onOpen(Session session, EndpointConfig config) { sessions.add(session); System.out.println("新客户端连接:" + session.getId()); // 注册消息处理器 session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { try { for (Session s : sessions) { if (s.isOpen()) { s.getBasicRemote().sendText("用户[" + session.getId() + "]:" + message); } } } catch (IOException e) { e.printStackTrace(); } } }); } @Override public void onClose(Session session, CloseReason closeReason) { sessions.remove(session); System.out.println("客户端断开:" + closeReason.getReasonPhrase()); } @Override public void onError(Session session, Throwable throwable) { System.err.println("WebSocket错误:" + throwable.getMessage()); } // 注册端点的方法,可在ServletContextListener中调用 public static void registerEndpoint(ServletContext context) { ServerEndpointConfig config = ServerEndpointConfig.Builder.create( ChatProgrammaticEndpoint.class, "/chat-programmatic" ).build(); ContainerProvider.getWebSocketContainer().addEndpoint(config); } }
然后在ServletContextListener的contextInitialized方法中调用ChatProgrammaticEndpoint.registerEndpoint(context)完成动态注册,同样兼容两个容器。
4. 容器适配的小细节
- Tomcat:确保Tomcat版本≥7.0.47(推荐用8.x或9.x),不需要额外配置,WAR包部署后自动识别WebSocket端点。
- WebLogic:WebLogic 12.1.3及以上版本原生支持JSR 356,如果遇到类加载优先级问题(比如应用的类和WebLogic自带类冲突),可以在
weblogic.xml中添加配置:
不过一般情况下,只要你的WAR包没有打包WebSocket实现类,这个配置不是必须的。<container-descriptor> <prefer-application-packages> <package-name>javax.websocket.*</package-name> </prefer-application-packages> </container-descriptor>
5. 验证兼容性
写一个简单的HTML客户端测试页面,放在项目的webapp目录下:
<!DOCTYPE html> <html> <head> <title>Chat Test</title> </head> <body> <input type="text" id="messageInput" placeholder="输入消息"/> <button onclick="sendMessage()">发送</button> <div id="messageArea"></div> <script> const ws = new WebSocket('ws://' + window.location.host + window.location.pathname + 'chat'); ws.onmessage = function(event) { const area = document.getElementById('messageArea'); area.innerHTML += '<p>' + event.data + '</p>'; }; function sendMessage() { const input = document.getElementById('messageInput'); ws.send(input.value); input.value = ''; } </script> </body> </html>
分别在Tomcat和WebLogic上部署项目,访问测试页面,验证消息收发是否正常。
内容的提问来源于stack exchange,提问作者Mirrana

