Servlet 是 Java Web 开发的核心技术,用于处理客户端请求并生成动态 Web 内容。本章将介绍 Servlet 的基本概念、生命周期和开发流程。
Maven 依赖配置
首先需要在 pom.xml 中添加 Servlet API 依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
第一个 Servlet 程序
下面是一个简单的 Servlet 示例:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// Servlet 初始化代码
System.out.println("HelloServlet 初始化");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 获取请求参数
String name = request.getParameter("name");
if (name == null || name.trim().isEmpty()) {
name = "访客";
}
// 获取输出流
PrintWriter out = response.getWriter();
// 生成 HTML 响应
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Hello Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello, " + name + "!</h1>");
out.println("<p>当前时间: " + new java.util.Date() + "</p>");
out.println("<a href=\"index.jsp\">返回首页</a>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
@Override
public void destroy() {
// Servlet 销毁代码
System.out.println("HelloServlet 销毁");
}
}
Servlet 生命周期详解
Servlet 生命周期包括初始化、服务和销毁三个阶段:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {
private int requestCount;
// 1. 初始化方法
@Override
public void init() throws ServletException {
super.init();
requestCount = 0;
System.out.println("Servlet 初始化完成,请求计数器已重置");
// 获取初始化参数
ServletConfig config = getServletConfig();
String initParam = config.getInitParameter("appName");
System.out.println("初始化参数 appName: " + initParam);
}
// 2. 服务方法 - 处理所有请求
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 在服务方法开始前执行
long startTime = System.currentTimeMillis();
// 调用父类的service方法,它会根据请求方法调用相应的doXxx方法
super.service(request, response);
// 在服务方法结束后执行
long endTime = System.currentTimeMillis();
long processingTime = endTime - startTime;
System.out.println("请求处理完成,耗时: " + processingTime + "ms");
}
// 3. 处理GET请求
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 增加请求计数
synchronized(this) {
requestCount++;
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>Servlet 生命周期</title></head>");
out.println("<body>");
out.println("<h1>Servlet 生命周期演示</h1>");
out.println("<p>总请求次数: " + requestCount + "</p>");
out.println("<p>Servlet 信息: " + getServletInfo() + "</p>");
out.println("<p>服务器信息: " + getServletContext().getServerInfo() + "</p>");
out.println("</body>");
out.println("</html>");
}
// 4. 处理POST请求
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取表单数据
String username = request.getParameter("username");
String email = request.getParameter("email");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>表单处理结果</title></head>");
out.println("<body>");
out.println("<h1>表单提交成功</h1>");
out.println("<p>用户名: " + username + "</p>");
out.println("<p>邮箱: " + email + "</p>");
out.println("<a href=\"lifecycle\">返回</a>");
out.println("</body>");
out.println("</html>");
}
// 5. 销毁方法
@Override
public void destroy() {
super.destroy();
System.out.println("Servlet 销毁,总请求次数: " + requestCount);
System.out.println("释放资源...");
}
// 6. 获取Servlet信息
@Override
public String getServletInfo() {
return "LifecycleServlet - 演示Servlet生命周期";
}
}
请求和响应对象
HttpServletRequest 和 HttpServletResponse 是 Servlet 开发中最重要的两个对象:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet("/request-info")
public class RequestInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>请求信息</title>");
out.println("<style>");
out.println("table { border-collapse: collapse; width: 100%; }");
out.println("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
out.println("th { background-color: #f2f2f2; }");
out.println("</style>");
out.println("</head>");
out.println("<body>");
out.println("<h1>HTTP 请求信息</h1>");
// 基本请求信息
out.println("<h2>基本请求信息</h2>");
out.println("<table>");
out.println("<tr><th>项目</th><th>值</th></tr>");
out.println("<tr><td>请求方法</td><td>" + request.getMethod() + "</td></tr>");
out.println("<tr><td>请求URL</td><td>" + request.getRequestURL() + "</td></tr>");
out.println("<tr><td>请求URI</td><td>" + request.getRequestURI() + "</td></tr>");
out.println("<tr><td>协议</td><td>" + request.getProtocol() + "</td></tr>");
out.println("<tr><td>服务器名</td><td>" + request.getServerName() + "</td></tr>");
out.println("<tr><td>服务器端口</td><td>" + request.getServerPort() + "</td></tr>");
out.println("<tr><td>客户端IP</td><td>" + request.getRemoteAddr() + "</td></tr>");
out.println("<tr><td>客户端主机</td><td>" + request.getRemoteHost() + "</td></tr>");
out.println("<tr><td>查询字符串</td><td>" + request.getQueryString() + "</td></tr>");
out.println("</table>");
// 请求头信息
out.println("<h2>请求头信息</h2>");
out.println("<table>");
out.println("<tr><th>头名称</th><th>头值</th></tr>");
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
out.println("<tr><td>" + headerName + "</td><td>" + headerValue + "</td></tr>");
}
out.println("</table>");
// 请求参数
out.println("<h2>请求参数</h2>");
out.println("<table>");
out.println("<tr><th>参数名</th><th>参数值</th></tr>");
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String paramName = entry.getKey();
String[] paramValues = entry.getValue();
out.println("<tr><td>" + paramName + "</td><td>" +
String.join(", ", paramValues) + "</td></tr>");
}
out.println("</table>");
// 属性信息
out.println("<h2>请求属性</h2>");
out.println("<table>");
out.println("<tr><th>属性名</th><th>属性值</th></tr>");
Enumeration<String> attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String attrName = attributeNames.nextElement();
Object attrValue = request.getAttribute(attrName);
out.println("<tr><td>" + attrName + "</td><td>" + attrValue + "</td></tr>");
}
out.println("</table>");
out.println("</body>");
out.println("</html>");
}
}
Servlet 配置和注解
Servlet 3.0 引入了注解配置,简化了部署描述符的使用:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
// 使用注解配置Servlet
@WebServlet(
name = "ConfigServlet",
urlPatterns = { "/config", "/configure" },
loadOnStartup = 1,
initParams = {
@WebInitParam(name = "databaseURL", value = "jdbc:mysql://localhost:3306/mydb"),
@WebInitParam(name = "maxConnections", value = "100"),
@WebInitParam(name = "timeout", value = "30")
}
)
public class ConfigServlet extends HttpServlet {
private String databaseURL;
private int maxConnections;
private int timeout;
@Override
public void init() throws ServletException {
super.init();
// 获取初始化参数
ServletConfig config = getServletConfig();
databaseURL = config.getInitParameter("databaseURL");
maxConnections = Integer.parseInt(config.getInitParameter("maxConnections"));
timeout = Integer.parseInt(config.getInitParameter("timeout"));
System.out.println("数据库URL: " + databaseURL);
System.out.println("最大连接数: " + maxConnections);
System.out.println("超时时间: " + timeout);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>Servlet 配置</title></head>");
out.println("<body>");
out.println("<h1>Servlet 配置信息</h1>");
out.println("<ul>");
out.println("<li>数据库URL: " + databaseURL + "</li>");
out.println("<li>最大连接数: " + maxConnections + "</li>");
out.println("<li>超时时间: " + timeout + "秒</li>");
out.println("<li>Servlet名称: " + getServletName() + "</li>");
out.println("</ul>");
out.println("</body>");
out.println("</html>");
}
}
Servlet 上下文和会话管理
ServletContext 和 HttpSession 用于在多个请求间共享数据:
import java.io.*;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet("/session-demo")
public class SessionDemoServlet extends HttpServlet {
private static final String VISIT_COUNT = "visitCount";
private static final String LAST_VISIT_TIME = "lastVisitTime";
// 应用级别的访问计数器
private AtomicInteger totalVisits;
@Override
public void init() throws ServletException {
super.init();
// 初始化应用级别的计数器
ServletContext context = getServletContext();
totalVisits = (AtomicInteger) context.getAttribute("totalVisits");
if (totalVisits == null) {
totalVisits = new AtomicInteger(0);
context.setAttribute("totalVisits", totalVisits);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 获取或创建会话
HttpSession session = request.getSession(true);
// 会话级别的访问计数
Integer visitCount = (Integer) session.getAttribute(VISIT_COUNT);
if (visitCount == null) {
visitCount = 1;
} else {
visitCount++;
}
session.setAttribute(VISIT_COUNT, visitCount);
session.setAttribute(LAST_VISIT_TIME, new java.util.Date());
// 应用级别的访问计数
int appVisitCount = totalVisits.incrementAndGet();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>会话管理</title>");
out.println("<style>");
out.println(".info { background-color: #f0f8ff; padding: 10px; margin: 10px 0; }");
out.println("</style>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet 会话管理演示</h1>");
// 会话信息
out.println("<div class=\"info\">");
out.println("<h2>会话信息</h2>");
out.println("<p>会话ID: " + session.getId() + "</p>");
out.println("<p>创建时间: " + new java.util.Date(session.getCreationTime()) + "</p>");
out.println("<p>最后访问时间: " + new java.util.Date(session.getLastAccessedTime()) + "</p>");
out.println("<p>最大不活动间隔: " + session.getMaxInactiveInterval() + "秒</p>");
out.println("<p>是否新会话: " + session.isNew() + "</p>");
out.println("</div>");
// 访问统计
out.println("<div class=\"info\">");
out.println("<h2>访问统计</h2>");
out.println("<p>本次会话访问次数: " + visitCount + "</p>");
out.println("<p>最后访问时间: " + session.getAttribute(LAST_VISIT_TIME) + "</p>");
out.println("<p>应用总访问次数: " + appVisitCount + "</p>");
out.println("</div>");
// 操作按钮
out.println("<div>");
out.println("<form method=\"post\" action=\"session-demo\">");
out.println("<button type=\"submit\" name=\"action\" value=\"refresh\">刷新页面</button>");
out.println("<button type=\"submit\" name=\"action\" value=\"invalidate\">销毁会话</button>");
out.println("</form>");
out.println("</div>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
if ("invalidate".equals(action)) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
response.sendRedirect("session-demo");
} else {
doGet(request, response);
}
}
}
过滤器 (Filter) 示例
过滤器用于在请求到达 Servlet 之前或响应发送到客户端之前进行处理:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebFilter("/*")
public class LoggingFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
System.out.println("LoggingFilter 初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
long startTime = System.currentTimeMillis();
// 记录请求信息
String clientIP = httpRequest.getRemoteAddr();
String requestURI = httpRequest.getRequestURI();
String method = httpRequest.getMethod();
System.out.println(String.format("请求开始 - IP: %s, 方法: %s, URI: %s, 时间: %s",
clientIP, method, requestURI, new java.util.Date()));
// 继续处理请求
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
long processingTime = endTime - startTime;
// 记录响应信息
System.out.println(String.format("请求完成 - URI: %s, 处理时间: %dms, 状态: %d",
requestURI, processingTime, httpResponse.getStatus()));
}
@Override
public void destroy() {
System.out.println("LoggingFilter 销毁");
}
}
关键知识点总结
- Servlet 生命周期:init() → service() → destroy()
- 请求处理:doGet(), doPost(), doPut(), doDelete() 等方法
- 注解配置:@WebServlet, @WebFilter, @WebListener
- 会话管理:HttpSession 用于跟踪用户状态
- 上下文对象:ServletContext 用于应用级别数据共享
- 过滤器:用于请求和响应的预处理和后处理
最佳实践
- 使用注解配置简化部署
- 合理管理会话数据,避免存储大量数据
- 及时释放资源,特别是在 destroy() 方法中
- 使用过滤器处理通用功能(如日志、认证、编码设置)
- 设置合适的字符编码防止中文乱码
- 合理使用重定向和请求转发
注意:在实际项目中,Servlet 通常与 JSP、MVC 框架(如 Spring MVC)结合使用,以更好地分离表现层和业务逻辑。
提示: 这是一个重要的概念,需要特别注意理解和掌握。
注意: 这是一个常见的错误点,请避免犯同样的错误。