- 用户进入/退出:用户输入昵称后进入聊天室,退出时广播通知。
- 实时消息发送与接收:所有在线用户可以实时看到其他用户发送的消息。
- 在线用户列表:实时显示当前所有在线的用户。
- 简单的界面:使用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>
如何运行
- 环境准备:确保您已经安装了 JDK 和 Tomcat 服务器。
- 创建项目:在Eclipse或IDEA中创建一个新的Dynamic Web Project。
- 导入文件:将上述所有Java源码和JSP/CSS文件按照项目结构放置到对应的位置。
- 部署项目:将项目部署到Tomcat服务器中。
- 启动服务器:启动Tomcat。
- 访问:在浏览器中输入
http://localhost:8080/YourProjectName/(将YourProjectName替换为您的项目名),由于web.xml中配置了welcome-file,它会自动跳转到login.jsp。
局限性与改进方向
这个聊天室是一个很好的入门项目,但它有以下局限性,可以作为您进一步学习和改进的方向:
- 实时性:它依赖于客户端定时刷新,用户体验不够流畅,且会增加服务器负载。
- 改进方案:使用 WebSocket,WebSocket是HTML5引入的一种在单个TCP连接上进行全双工通信的协议,可以实现真正的实时双向通信,是现代聊天应用的标准技术。
- 数据持久化:所有用户和消息都存储在服务器的内存中,一旦服务器重启,所有数据都会丢失。
- 改进方案:集成数据库(如MySQL、H2),将用户信息和聊天记录保存到数据库中。
- 架构模式:使用的是较老的Model 1模式(JSP + JavaBean),JSP页面中混杂了大量的Java代码,不利于维护。
- 改进方案:采用更现代的 MVC (Model-View-Controller) 架构,使用 Servlet 3.0+、JSTL (JSP Standard Tag Library) 和 EL (Expression Language) 来分离逻辑和表现层,或者更进一步,使用 Spring MVC 框架。
- 安全性:没有做任何安全防护,容易受到XSS(跨站脚本攻击)等威胁。
- 改进方案:对用户输入进行过滤和转义,可以使用JSTL的
fn:escapeXml()函数或后端进行StringEscapeUtils.escapeHtml4()处理。
- 改进方案:对用户输入进行过滤和转义,可以使用JSTL的
希望这份详细的源码和解释能帮助您成功搭建并理解JSP聊天室的工作原理!
