用JSP打造一个简单留言板:从目录结构到代码实现,深入JSP语法与Servlet交互
今天咱们来动手写一个简单的留言板,用JSP实现。功能不复杂:用户提交姓名和留言,页面展示所有留言。咱们会从目录结构讲起,配好环境,然后把代码细节讲透。特别地,这次我会重点聊聊JSP页面咋引用Servlet里的对象,还有JSP里的语法现象,顺便跟Spring Boot的思路做点对比。你用惯了Spring Boot的话,这篇能让你既摸到JSP的"老底子",又看到现代框架的影子。
目录结构:项目架子长啥样
咱们用标准的Java Web项目结构,跑在Tomcat上。目录是这样的:
bash
message-board/
├── src/
│ └── main/
│ └── java/
│ └── com.example/
│ ├── Message.java # 留言实体类
│ └── MessageServlet.java # 处理请求的Servlet
├── web/
│ ├── WEB-INF/
│ │ ├── web.xml # 部署描述文件
│ │ └── views/
│ │ ├── index.jsp # 主页面,表单+留言展示
│ │ └── messages.jsp # 留言列表片段
│ └── resources/
│ └── style.css # 简单样式
└── pom.xml # Maven配置文件(可选)
- src/main/java:Java代码放这儿,比如实体类和Servlet。
- web/WEB-INF :核心地带,
web.xml
配置Servlet,views
下放JSP。 - web/resources:静态资源,比如CSS。
- pom.xml:用Maven管依赖,不用的话手动加jar包也行。
这布局跟Spring Boot有点像,但少了自动配置,得自己动手搭。
配置环境:跑起来前的准备
JSP得跑在Servlet容器里,咱们用Tomcat,配好web.xml
和依赖。
1. 配置web.xml
web.xml
是项目的"指挥官",告诉Tomcat咋处理请求:
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">
<servlet>
<servlet-name>MessageServlet</servlet-name>
<servlet-class>com.example.MessageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MessageServlet</servlet-name>
<url-pattern>/message</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>WEB-INF/views/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<servlet>
:注册MessageServlet
,处理/message
请求。<welcome-file-list>
:默认打开index.jsp
。
2. Maven依赖(可选)
用Maven的话,pom.xml
加这些:
xml
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
这些是Servlet和JSP的API,Tomcat自带实现,所以用provided
。
3. 部署Tomcat
打成war包,丢到Tomcat的webapps
目录,启动后访问http://localhost:8080/message-board/
。
代码实现:从实体到页面
功能简单:提交留言,展示列表。用内存存数据(实际得用数据库)。
1. 实体类:Message.java
留言的数据结构:
java
package com.example;
public class Message {
private String username;
private String content;
public Message(String username, String content) {
this.username = username;
this.content = content;
}
public String getUsername() { return username; }
public String getContent() { return content; }
public void setUsername(String username) { this.username = username; }
public void setContent(String content) { this.content = content; }
}
2. Servlet:MessageServlet.java
Servlet当Controller,处理逻辑:
java
package com.example;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MessageServlet extends HttpServlet {
private List<Message> messages = new ArrayList<>(); // 内存存留言
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("messages", messages); // 传数据给JSP
req.getRequestDispatcher("/WEB-INF/views/index.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String content = req.getParameter("content");
if (username != null && content != null && !username.trim().isEmpty() && !content.trim().isEmpty()) {
messages.add(new Message(username, content));
}
resp.sendRedirect(req.getContextPath() + "/message"); // 重定向刷新
}
}
doGet
:把messages
塞进request,转发到JSP。doPost
:取参数,存留言,重定向避免重复提交。
3. 主页面:index.jsp
表单和留言展示:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>留言板</title>
<link rel="stylesheet" href="../resources/style.css">
</head>
<body>
<h1>欢迎留言</h1>
<form action="${pageContext.request.contextPath}/message" method="post">
<label>姓名:</label>
<input type="text" name="username"><br>
<label>留言:</label>
<textarea name="content"></textarea><br>
<input type="submit" value="提交">
</form>
<h2>所有留言</h2>
<jsp:include page="messages.jsp"/>
</body>
</html>
<form>
:提交到/message
,走POST。<jsp:include>
:动态引入留言列表。
4. 留言列表:messages.jsp
展示Servlet传来的数据:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.example.Message" %>
<%@ page import="java.util.List" %>
<% List<Message> messages = (List<Message>) request.getAttribute("messages"); %>
<% if (messages != null && !messages.isEmpty()) { %>
<ul>
<% for (Message msg : messages) { %>
<li><strong><%= msg.getUsername() %>:</strong> <%= msg.getContent() %></li>
<% } %>
</ul>
<% } else { %>
<p>暂无留言,快来抢沙发吧!</p>
<% } %>
- 用脚本取
messages
,循环展示。
5. 样式:style.css
简单美化:
css
body { font-family: Arial, sans-serif; margin: 20px; }
h1, h2 { color: #333; }
form { margin-bottom: 20px; }
input[type="text"], textarea { width: 200px; margin: 5px 0; }
ul { list-style: none; padding: 0; }
li { padding: 10px; border-bottom: 1px solid #ddd; }
重点:JSP咋引用Servlet的对象?
Servlet和JSP的交互靠请求作用域(request scope)。具体咋弄:
-
Servlet传对象
- 在
MessageServlet
的doGet
里,用req.setAttribute("messages", messages)
把留言列表塞进request。 forward
转发到JSP时,这个对象就带过去了。
- 在
-
JSP里取对象
- 脚本方式 :
messages.jsp
里用<% List<Message> messages = (List<Message>) request.getAttribute("messages"); %>
,强转后就能用。 - EL表达式 :更现代点,直接
${messages}
取值。比如${messages[0].username}
拿第一条留言的用户名。 - 区别:脚本得手动写Java代码,EL更简洁,推荐用EL。
- 脚本方式 :
-
取值细节
request.getAttribute()
返回Object,得强转。- EL自动解析对象属性,走getter方法,比如
msg.getUsername()
被${msg.username}
替代。
-
实际例子
改下
messages.jsp
,用EL试试:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% if (request.getAttribute("messages") != null && !((List<?>) request.getAttribute("messages")).isEmpty()) { %>
<ul>
${messages.stream().map(msg -> '<li><strong>' + msg.username + ':</strong> ' + msg.content + '</li>').join('')}
</ul>
<% } else { %>
<p>暂无留言,快来抢沙发吧!</p>
<% } %>
但注意,JSP里EL不支持直接写循环,得用JSTL(下面会讲),这里只是展示EL取值的思路。
JSP语法现象:这些你得知道
JSP的语法挺有特色,咱们拎几个常见的讲讲:
-
脚本元素(<% %>)
<% %>
:写Java代码,比如逻辑判断、循环。<%= %>
:输出变量值,等于out.print()
,如<%= msg.getUsername() %>
。<%! %>
:声明成员变量或方法,放类级别,比如<%! int count = 0; %>
。- 现象:脚本多了页面乱,维护难,Spring Boot里Thymeleaf完全抛弃了这套。
-
EL表达式(${})
- 语法:
${对象.属性}
,从request、session等作用域取值。 - 例子:
${messages.size()}
取列表长度。 - 现象:比脚本简洁,但功能有限,复杂逻辑得靠JSTL。
- 语法:
-
JSTL(标签库)
加个
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
,就能用<c:forEach>
循环。比如改messages.jsp
:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:choose>
<c:when test="${not empty messages}">
<ul>
<c:forEach var="msg" items="${messages}">
<li><strong>${msg.username}:</strong> ${msg.content}</li>
</c:forEach>
</ul>
</c:when>
<c:otherwise>
<p>暂无留言,快来抢沙发吧!</p>
</c:otherwise>
</c:choose>
- 现象 :代码干净了,跟Spring Boot里Thymeleaf的
th:each
很像。
- 隐式对象
- JSP内置对象:
request
、response
、session
、application
等。 - 例子:
request.getAttribute()
取Servlet传来的值,pageContext.request.contextPath
拿项目路径。 - 现象:这些对象是Servlet环境自带的,Spring Boot里注解直接注入类似功能。
- JSP内置对象:
跑起来:效果咋样?
部署到Tomcat,访问http://localhost:8080/message-board/message
,输入姓名和留言,提交后列表就出来了。简单直接,但功能全。
总结:JSP的实用与局限
这留言板是不是挺好上手?Servlet当Controller,JSP管展示,数据靠request传。JSP的脚本、EL、JSTL各有特色,但脚本多了乱,复用靠include硬凑,扩展性一般。换Spring Boot,注解取代Servlet,Thymeleaf干掉脚本,体验直接拉满。不过JSP这套路,是不是让你对Spring Boot的底层多了点熟悉感?从这儿走来的技术,根儿还是相通的!