1. 用户进入/退出:用户输入昵称后进入聊天室,退出时广播通知。
  2. 实时消息发送与接收:所有在线用户可以实时看到其他用户发送的消息。
  3. 在线用户列表:实时显示当前所有在线的用户。
  4. 简单的界面:使用HTML和CSS构建,清爽简洁。

核心技术点:

  • JSP (JavaServer Pages): 用于显示动态页面,如聊天消息列表和在线用户列表。
  • Servlet: 作为控制器,处理用户的请求(如发送消息、退出聊天)。
  • JavaBean (ChatBean.java): 封装聊天室的核心业务逻辑,如管理用户列表、消息列表等。
  • HTTP会话: 使用 HttpSession 来跟踪每个用户的状态和身份。
  • 定时刷新: 通过 <meta> 标签实现客户端定时刷新,模拟“实时”效果,这是一种简单但有效的实现方式。

项目结构

在您的IDE(如Eclipse或IntelliJ IDEA)中,创建一个Dynamic Web Project,并按照以下结构组织文件:

ChatRoom/
├── src/
│   └── com/
│       └── example/
│           └── ChatBean.java      <-- JavaBean,核心业务逻辑
├── WebContent/
│   ├── META-INF/
│   │   └── MANIFEST.MF
│   ├── index.jsp                  <-- 聊天室主页面
│   ├── login.jsp                  <-- 登录/进入聊天室页面
│   ├── style.css                  <-- 样式表
│   └── WEB-INF/
│       └── web.xml                <-- 部署描述符,配置Servlet

源码详解

JavaBean - ChatBean.java

这个类是聊天室的“大脑”,它不依赖任何Web API(如Servlet或JSP),是纯的业务逻辑,便于测试和重用。

src/com/example/ChatBean.java

package com.example;
import java.util.ArrayList;
import java.util.List;
/**
 * 聊天室核心业务逻辑Bean
 * 负责管理用户列表和消息列表
 */
public class ChatBean {
    // 存储所有在线用户昵称的列表
    private List<String> userList = new ArrayList<>();
    // 存储所有聊天消息的列表
    private List<String> messageList = new ArrayList<>();
    /**
     * 用户登录聊天室
     * @param nickname 用户昵称
     */
    public void login(String nickname) {
        if (!userList.contains(nickname)) {
            userList.add(nickname);
            // 广播新用户加入的消息
            addSystemMessage(nickname + " 加入了聊天室。");
        }
    }
    /**
     * 用户退出聊天室
     * @param nickname 用户昵称
     */
    public void logout(String nickname) {
        if (userList.remove(nickname)) {
            // 广播用户退出的消息
            addSystemMessage(nickname + " 离开了聊天室。");
        }
    }
    /**
     * 发送一条聊天消息
     * @param sender 发送者昵称
     * @param message 消息内容
     */
    public void sendMessage(String sender, String message) {
        String fullMessage = "<b>" + sender + ":</b> " + message;
        messageList.add(fullMessage);
    }
    /**
     * 添加一条系统消息(如用户加入/退出)
     * @param systemMessage 系统消息内容
     */
    public void addSystemMessage(String systemMessage) {
        messageList.add("<i style='color: #888;'>" + systemMessage + "</i>");
    }
    // --- Getter方法,供JSP页面调用 ---
    public List<String> getUserList() {
        return userList;
    }
    public List<String> getMessageList() {
        return messageList;
    }
}

Servlet - ChatServlet.java

Servlet负责接收HTTP请求,调用ChatBean进行处理,然后转发到JSP页面显示结果。

src/com/example/ChatServlet.java

package com.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/ChatServlet") // 使用注解映射URL,无需在web.xml中配置
public class ChatServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理GET请求,主要用于刷新页面和退出
        processRequest(request, response);
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理POST请求,主要用于发送消息
        processRequest(request, response);
    }
    private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取或创建ChatBean实例(作为聊天室的全局对象)
        // synchronized保证线程安全,防止多用户同时操作导致数据错乱
        ChatBean chatBean;
        synchronized (getServletContext()) {
            chatBean = (ChatBean) getServletContext().getAttribute("chatBean");
            if (chatBean == null) {
                chatBean = new ChatBean();
                getServletContext().setAttribute("chatBean", chatBean);
            }
        }
        // 2. 获取当前用户的会话
        HttpSession session = request.getSession();
        String nickname = (String) session.getAttribute("nickname");
        // 3. 根据请求参数执行不同操作
        String action = request.getParameter("action");
        if ("send".equals(action) && nickname != null) {
            // 发送消息
            String message = request.getParameter("message");
            if (message != null && !message.trim().isEmpty()) {
                chatBean.sendMessage(nickname, message);
            }
        } else if ("exit".equals(action)) {
            // 退出聊天
            if (nickname != null) {
                chatBean.logout(nickname);
                session.invalidate(); // 销毁会话,使用户下线
                response.sendRedirect("login.jsp"); // 重定向到登录页
                return; // 结束处理
            }
        }
        // 4. 请求处理完毕,将控制权交还给index.jsp进行页面渲染
        request.getRequestDispatcher("index.jsp").forward(request, response);
    }
}

JSP 页面

login.jsp - 用户进入聊天室的入口

WebContent/login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">聊天室 - 登录</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <div class="container">
        <h1>欢迎来到简易聊天室</h1>
        <form action="ChatServlet" method="post">
            <input type="hidden" name="action" value="login">
            <label for="nickname">请输入您的昵称:</label>
            <input type="text" id="nickname" name="nickname" required>
            <button type="submit">进入聊天室</button>
        </form>
    </div>
</body>
</html>

index.jsp - 聊天室主页面

这个页面是核心,它会从ChatBean中获取数据并展示,它也包含一个隐藏的<form>用于处理发送消息和退出的请求。

WebContent/index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.example.ChatBean" %>
<%@ page import="java.util.List" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">简易聊天室</title>
<!-- 每3秒自动刷新一次页面,实现“实时”效果 -->
<meta http-equiv="refresh" content="3;URL=index.jsp">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <%
        // 从ServletContext中获取ChatBean实例
        ChatBean chatBean = (ChatBean) getServletContext().getAttribute("chatBean");
        if (chatBean == null) {
            // 如果没有,说明是第一次进入,重定向到登录页
            response.sendRedirect("login.jsp");
            return;
        }
        // 从当前用户的会话中获取昵称
        String nickname = (String) session.getAttribute("nickname");
        if (nickname == null) {
            // 如果会话中没有昵称,说明未登录,重定向到登录页
            response.sendRedirect("login.jsp");
            return;
        }
    %>
    <div class="chat-container">
        <div class="header">
            <h2>聊天室 (<%= nickname %>)</h2>
            <form action="ChatServlet" method="post" style="display: inline;">
                <input type="hidden" name="action" value="exit">
                <button type="submit" class="exit-btn">退出</button>
            </form>
        </div>
        <div class="main-content">
            <!-- 左侧:聊天消息区 -->
            <div class="chat-messages">
                <%
                    List<String> messages = chatBean.getMessageList();
                    if (messages != null && !messages.isEmpty()) {
                        for (String msg : messages) {
                %>
                            <div class="message"><%= msg %></div>
                <%
                        }
                    } else {
                %>
                            <div class="message">欢迎!开始聊天吧~</div>
                <%
                    }
                %>
            </div>
            <!-- 右侧:在线用户列表 -->
            <div class="online-users">
                <h3>在线用户 (<%= chatBean.getUserList().size() %>)</h3>
                <ul>
                    <%
                        List<String> users = chatBean.getUserList();
                        if (users != null && !users.isEmpty()) {
                            for (String user : users) {
                    %>
                                <li><%= user %></li>
                    <%
                            }
                        }
                    %>
                </ul>
            </div>
        </div>
        <!-- 底部:消息发送区 -->
        <div class="message-form">
            <form action="ChatServlet" method="post" onsubmit="return sendMessage(this);">
                <input type="hidden" name="action" value="send">
                <input type="text" name="message" placeholder="输入消息..." required autofocus>
                <button type="submit">发送</button>
            </form>
        </div>
    </div>
    <script>
        // 将滚动条滚动到最底部
        window.onload = function() {
            var messagesDiv = document.querySelector('.chat-messages');
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        };
        function sendMessage(form) {
            var messageInput = form.querySelector('input[name="message"]');
            if (messageInput.value.trim() === '') {
                return false; // 阻止空消息提交
            }
            return true; // 允许提交
        }
    </script>
</body>
</html>

CSS 样式 - style.css

为了美化界面,添加一个简单的样式表。

WebContent/style.css

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    margin: 0;
    padding: 20px;
    color: #333;
}
.container, .chat-container {
    max-width: 800px;
    margin: 0 auto;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}
/* Login Page Styles */
.container h1 {
    text-align: center;
    color: #1a73e8;
    padding: 20px;
}
.container form {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
}
.container label {
    margin-bottom: 10px;
    font-weight: bold;
}
.container input[type="text"] {
    padding: 10px;
    width: 100%;
    max-width: 300px;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-bottom: 20px;
}
.container button {
    padding: 10px 20px;
    background-color: #1a73e8;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
}
.container button:hover {
    background-color: #155ab6;
}
/* Chat Room Styles */
.chat-container .header {
    background-color: #1a73e8;
    color: white;
    padding: 15px 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.chat-container .header h2 {
    margin: 0;
}
.exit-btn {
    background-color: #dc3545;
    color: white;
    border: none;
    padding: 8px 15px;
    border-radius: 4px;
    cursor: pointer;
    text-decoration: none;
}
.exit-btn:hover {
    background-color: #c82333;
}
.chat-container .main-content {
    display: flex;
    height: 400px;
}
.chat-messages {
    flex: 3;
    padding: 20px;
    overflow-y: auto;
    border-right: 1px solid #eee;
}
.message {
    padding: 8px 12px;
    margin-bottom: 10px;
    border-radius: 18px;
    background-color: #e9ecef;
    line-height: 1.4;
}
.message b {
    color: #1a73e8;
}
.message i {
    font-style: normal;
    color: #6c757d;
}
.online-users {
    flex: 1;
    padding: 20px;
    background-color: #f8f9fa;
}
.online-users h3 {
    margin-top: 0;
    border-bottom: 1px solid #dee2e6;
    padding-bottom: 10px;
}
.online-users ul {
    list-style: none;
    padding: 0;
    margin: 0;
}
.online-users li {
    padding: 5px 0;
    border-bottom: 1px solid #eee;
}
.message-form {
    padding: 15px 20px;
    border-top: 1px solid #eee;
    display: flex;
}
.message-form form {
    display: flex;
    width: 100%;
}
.message-form input[type="text"] {
    flex: 1;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 20px 0 0 20px;
    outline: none;
}
.message-form button {
    padding: 10px 20px;
    background-color: #1a73e8;
    color: white;
    border: none;
    border-radius: 0 20px 20px 0;
    cursor: pointer;
}
.message-form button:hover {
    background-color: #155ab6;
}

web.xml 部署描述符

虽然我们使用了@WebServlet注解,但为了完整性,这里提供一个web.xml的示例,如果使用注解,这个文件可以省略或保持为空。

WebContent/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>ChatRoom</display-name>
    <!-- 如果不使用@WebServlet注解,可以在这里配置Servlet -->
    <!--
    <servlet>
        <servlet-name>ChatServlet</servlet-name>
        <servlet-class>com.example.ChatServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ChatServlet</servlet-name>
        <url-pattern>/ChatServlet</url-pattern>
    </servlet-mapping>
    -->
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>
</web-app>

如何运行

  1. 环境准备:确保您已经安装了 JDKTomcat 服务器。
  2. 创建项目:在Eclipse或IDEA中创建一个新的Dynamic Web Project。
  3. 导入文件:将上述所有Java源码和JSP/CSS文件按照项目结构放置到对应的位置。
  4. 部署项目:将项目部署到Tomcat服务器中。
  5. 启动服务器:启动Tomcat。
  6. 访问:在浏览器中输入 http://localhost:8080/YourProjectName/(将YourProjectName替换为您的项目名),由于web.xml中配置了welcome-file,它会自动跳转到login.jsp

局限性与改进方向

这个聊天室是一个很好的入门项目,但它有以下局限性,可以作为您进一步学习和改进的方向:

  1. 实时性:它依赖于客户端定时刷新,用户体验不够流畅,且会增加服务器负载。
    • 改进方案:使用 WebSocket,WebSocket是HTML5引入的一种在单个TCP连接上进行全双工通信的协议,可以实现真正的实时双向通信,是现代聊天应用的标准技术。
  2. 数据持久化:所有用户和消息都存储在服务器的内存中,一旦服务器重启,所有数据都会丢失。
    • 改进方案:集成数据库(如MySQL、H2),将用户信息和聊天记录保存到数据库中。
  3. 架构模式:使用的是较老的Model 1模式(JSP + JavaBean),JSP页面中混杂了大量的Java代码,不利于维护。
    • 改进方案:采用更现代的 MVC (Model-View-Controller) 架构,使用 Servlet 3.0+JSTL (JSP Standard Tag Library)EL (Expression Language) 来分离逻辑和表现层,或者更进一步,使用 Spring MVC 框架。
  4. 安全性:没有做任何安全防护,容易受到XSS(跨站脚本攻击)等威胁。
    • 改进方案:对用户输入进行过滤和转义,可以使用JSTL的fn:escapeXml()函数或后端进行StringEscapeUtils.escapeHtml4()处理。

希望这份详细的源码和解释能帮助您成功搭建并理解JSP聊天室的工作原理!