作为后端Java工程师,Servlet是构建动态Web应用的核心组件。本文将从基础概念到高级特性,结合代码案例系统梳理Servlet知识体系。
一、Servlet基础概念
1. Servlet定义与作用
-
定义:Servlet是Java EE(现Jakarta EE)规范的一部分,是运行在Web服务器上的Java程序,用于处理HTTP请求并生成动态响应。
-
作用:
- 接收客户端请求(如HTML表单提交)
- 执行业务逻辑(如数据库操作)
- 生成动态响应(如HTML页面、JSON数据)
2. Servlet与CGI对比
| 特性 | Servlet | CGI |
|---|---|---|
| 进程模型 | 单实例多线程 | 每请求创建新进程 |
| 性能 | 高(线程切换开销小) | 低(进程创建开销大) |
| 资源占用 | 低(共享JVM内存) | 高(每个进程独立内存) |
| 开发复杂度 | 低(使用Java API) | 高(需处理进程间通信) |
二、Servlet核心组件
1. Servlet生命周期
- 加载与实例化:首次请求时由容器加载并实例化
- 初始化(init) :执行一次,用于资源初始化
- 服务(service) :处理每个请求,调用doGet/doPost等方法
- 销毁(destroy) :应用关闭时释放资源
示例代码:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class LifeCycleServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("Servlet初始化");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().println("<h1>Servlet服务中</h1>");
}
@Override
public void destroy() {
System.out.println("Servlet销毁");
}
}
2. 请求与响应对象
- HttpServletRequest:获取请求参数、头信息、会话等
- HttpServletResponse:设置响应状态、头信息、输出内容
示例代码:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求参数
String name = req.getParameter("name");
// 设置响应内容类型
resp.setContentType("text/html;charset=UTF-8");
// 输出响应内容
PrintWriter out = resp.getWriter();
out.println("<h1>Hello, " + name + "!</h1>");
}
3. 会话管理(HttpSession)
- 创建会话:
request.getSession(true) - 存储数据:
session.setAttribute("key", value) - 获取数据:
session.getAttribute("key") - 销毁会话:
session.invalidate()
示例代码:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(true);
// 首次访问时设置用户名
if (session.getAttribute("username") == null) {
session.setAttribute("username", "Guest");
}
resp.getWriter().println("当前用户: " + session.getAttribute("username"));
}
三、Servlet配置与部署
1. 传统配置方式(web.xml)
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!-- 启动时加载 -->
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
2. 注解配置方式(Servlet 3.0+)
@WebServlet(
name = "HelloServlet",
urlPatterns = {"/hello", "/hi"},
initParams = {
@WebInitParam(name = "adminEmail", value = "admin@example.com")
},
loadOnStartup = 1
)
public class HelloServlet extends HttpServlet {
// ...
}
3. 部署到Tomcat
-
打包为WAR文件:
mvn clean package -
部署到Tomcat:
- 复制WAR文件到
$CATALINA_HOME/webapps/ - 或通过IDE直接运行(如IntelliJ的Tomcat配置)
- 复制WAR文件到
四、Servlet高级特性
1. 线程安全问题
-
原因:Servlet实例单例,多线程共享成员变量
-
解决方案:
- 避免使用成员变量存储请求级数据
- 使用
ThreadLocal存储线程本地变量 - 对共享资源加锁(如
synchronized)
错误示例(线程不安全):
public class UnsafeServlet extends HttpServlet {
private int count = 0; // 成员变量,多线程共享
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
count++; // 线程不安全
resp.getWriter().println("Count: " + count);
}
}
修正方案(使用局部变量):
public class SafeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
int count = 0; // 局部变量,线程安全
count++;
resp.getWriter().println("Count: " + count);
}
}
2. 过滤器(Filter)
- 作用:拦截请求/响应,实现日志记录、权限校验等
- 示例代码:
@WebFilter("/*") // 拦截所有请求
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("请求URI: " + ((HttpServletRequest) request).getRequestURI());
chain.doFilter(request, response); // 继续处理
}
}
3. 监听器(Listener)
- 作用:监听应用、会话、请求的生命周期事件
- 示例代码:
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("应用启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("应用关闭");
}
}
五、Servlet实战案例
案例1:用户登录系统
- 前端表单:
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
- Servlet处理:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
// 简单校验(实际项目应连接数据库)
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功,设置会话
HttpSession session = req.getSession(true);
session.setAttribute("user", username);
resp.sendRedirect("/welcome");
} else {
resp.getWriter().println("用户名或密码错误");
}
}
}
- 欢迎页面:
@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(false); // 不创建新会话
if (session == null || session.getAttribute("user") == null) {
resp.sendRedirect("/login");
} else {
resp.getWriter().println("欢迎, " + session.getAttribute("user"));
}
}
}
案例2:文件上传
- 前端表单:
<form action="/upload" method="post" enctype="multipart/form-data">
文件: <input type="file" name="file"><br>
<input type="submit" value="上传">
</form>
- Servlet处理(需引入Apache Commons FileUpload):
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Part filePart = req.getPart("file");
String fileName = filePart.getSubmittedFileName();
// 保存文件到服务器
try (InputStream fileContent = filePart.getInputStream();
FileOutputStream out = new FileOutputStream("/uploads/" + fileName)) {
IOUtils.copy(fileContent, out); // 使用Apache Commons IO
}
resp.getWriter().println("文件上传成功: " + fileName);
}
}
六、Servlet与现代框架
-
与Spring MVC的关系:
- Servlet是底层协议处理器
- Spring MVC的
DispatcherServlet继承自HttpServlet - Spring MVC通过注解(如
@Controller)简化Servlet开发
-
与Jakarta EE的关系:
- Servlet是Jakarta EE的核心组件
- Jakarta EE 9+将包名从
javax.servlet改为jakarta.servlet
七、总结
Servlet是Java Web开发的基石,掌握其核心原理和实战技巧对后端工程师至关重要。本文系统梳理了:
- Servlet生命周期与核心组件
- 请求/响应处理与会话管理
- 配置方式与部署流程
- 线程安全与高级特性
- 实战案例(登录、文件上传)
在实际开发中,建议结合Spring Boot等框架简化Servlet开发,但理解底层原理有助于解决复杂问题。后续将分享更多Servlet与微服务架构的集成案例。