Java Web 应用的生命周期详解
一、Java Web 应用生命周期概述
Java Web 应用的生命周期可以分为三个主要阶段:加载与初始化阶段、请求处理阶段 和 销毁阶段。这些阶段涉及不同的组件(如 Servlet、Filter、Listener 等)以及它们的交互方式。
在实际开发中,理解生命周期对于优化性能、管理资源和设计架构至关重要。接下来,我们将详细分析每个阶段的具体内容,并提供更详细的扩写和代码示例。
二、加载与初始化阶段
当服务器启动时,Web 容器(如 Tomcat、Jetty)会加载并初始化 Java Web 应用。这一阶段的主要任务是为应用创建运行环境。
1. 加载配置文件
容器会读取 web.xml
文件或基于注解的配置信息。web.xml
是传统的部署描述符文件,定义了应用的结构和组件的映射关系。现代开发中,也可以通过注解(如 @WebServlet
、@WebFilter
)来替代部分配置。
示例代码(基于注解的 Servlet 配置):
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 java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.getWriter().println("<h1>Hello, World!</h1>");
}
}
注解的优势:
- 简化配置:无需手动编辑
web.xml
文件。 - 动态性:可以在运行时动态加载组件。
2. 创建 ServletContext
容器会为整个应用创建一个全局的上下文对象 ServletContext
。这个对象在整个应用生命周期中始终存在,用于存储共享数据和资源。
示例代码(使用 ServletContext 存储数据):
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("Application is starting...");
sce.getServletContext().setAttribute("appName", "MyApp"); // 设置全局属性
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Application is stopping...");
}
}
在 Servlet 中访问全局属性:
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 java.io.IOException;
@WebServlet("/contextExample")
public class ContextExampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String appName = getServletContext().getAttribute("appName").toString();
resp.setContentType("text/html");
resp.getWriter().println("<h1>Application Name: " + appName + "</h1>");
}
}
3. 初始化监听器
如果定义了 ServletContextListener
,容器会在应用启动时调用其 contextInitialized()
方法。监听器可以用来执行一些初始化逻辑,例如加载配置文件、初始化数据库连接池等。
监听器的作用:
- 初始化资源:加载配置文件、初始化数据库连接池。
- 监控应用状态:记录日志、统计应用启动时间。
示例代码(加载配置文件):
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.Properties;
public class ConfigLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
try {
Properties props = new Properties();
props.load(getClass().getClassLoader().getResourceAsStream("config.properties"));
sce.getServletContext().setAttribute("config", props);
System.out.println("Configuration loaded successfully.");
} catch (Exception e) {
System.err.println("Failed to load configuration: " + e.getMessage());
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Application is stopping...");
}
}
4. 初始化过滤器和 Servlet
容器会根据配置顺序初始化所有过滤器和 Servlet。每个 Servlet 的 init()
方法会被调用一次,用于完成初始化工作。
示例代码(Servlet 初始化):
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@WebServlet("/initExample")
public class InitExampleServlet extends HttpServlet {
private String message;
@Override
public void init() throws ServletException {
System.out.println("Servlet is initializing...");
message = getInitParameter("message"); // 获取初始化参数
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.getWriter().println("<h1>" + message + "</h1>");
}
}
在 web.xml
中配置初始化参数:
<servlet>
<servlet-name>initExample</servlet-name>
<servlet-class>com.example.InitExampleServlet</servlet-class>
<init-param>
<param-name>message</param-name>
<param-value>Welcome to My App</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>initExample</servlet-name>
<url-pattern>/initExample</url-pattern>
</servlet-mapping>
三、请求处理阶段
当客户端发起 HTTP 请求时,容器会根据 URL 映射将请求分发到对应的 Servlet 或 JSP 页面。
1. 匹配 URL 模式
容器会根据 web.xml
或注解中的路径映射找到目标 Servlet。例如,/hello
路径会映射到 HelloServlet
。
URL 映射规则:
- 精确匹配:
/hello
匹配/hello
路径。 - 后缀匹配:
*.jsp
匹配所有以.jsp
结尾的路径。 - 通配符匹配:
/*
匹配所有路径。
2. 执行过滤器链
如果有过滤器,容器会按顺序调用它们的 doFilter()
方法。过滤器可以对请求和响应进行预处理或后处理。
示例代码(过滤器链):
import javax.servlet.*;
import java.io.IOException;
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LoggingFilter is initializing...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Before processing request...");
long startTime = System.currentTimeMillis();
chain.doFilter(request, response); // 继续处理请求
long endTime = System.currentTimeMillis();
System.out.println("Request processed in " + (endTime - startTime) + " ms");
}
@Override
public void destroy() {
System.out.println("LoggingFilter is being destroyed...");
}
}
过滤器链的作用:
- 日志记录:记录请求的时间、来源和内容。
- 权限控制:检查用户是否有权限访问特定资源。
- 数据转换:修改请求或响应的内容格式。
3. 调用 Servlet 的服务方法
容器会调用目标 Servlet 的 service()
方法,进而调用 doGet()
或 doPost()
方法。开发者可以在这些方法中编写业务逻辑。
示例代码(Servlet 处理请求):
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 java.io.IOException;
@WebServlet("/processRequest")
public class ProcessRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name"); // 获取请求参数
if (name == null || name.isEmpty()) {
name = "Guest";
}
resp.setContentType("text/html");
resp.getWriter().println("<h1>Hello, " + name + "!</h1>");
}
}
请求参数解析:
- 使用
req.getParameter(String name)
获取请求参数。 - 支持多种编码方式(如 UTF-8),确保国际化支持。
4. 生成响应
Servlet 或 JSP 页面会生成响应内容并返回给客户端。响应可以是 HTML、JSON、XML 等格式。
示例代码(JSON 响应):
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 java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/jsonResponse")
public class JsonResponseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();
writer.println("{\"status\": \"success\", \"message\": \"Hello, JSON!\"}");
}
}
JSON 响应的优点:
- 轻量级:适合移动端和 API 开发。
- 跨平台:兼容多种编程语言。
四、销毁阶段
当服务器关闭或应用被卸载时,容器会销毁所有组件。
1. 调用 Servlet 的 destroy()
方法
容器会调用每个 Servlet 的 destroy()
方法,释放资源。
示例代码(Servlet 销毁):
@Override
public void destroy() {
System.out.println("Servlet is being destroyed...");
// 释放资源
}
销毁阶段的任务:
- 关闭数据库连接。
- 清理缓存数据。
- 释放占用的内存。
2. 调用过滤器的 destroy()
方法
容器会调用每个过滤器的 destroy()
方法,清理相关资源。
示例代码(过滤器销毁):
@Override
public void destroy() {
System.out.println("Filter is being destroyed...");
}
3. 调用监听器的 contextDestroyed()
方法
容器会调用 ServletContextListener
的 contextDestroyed()
方法,通知应用即将关闭。
示例代码(监听器销毁):
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Application is stopping...");
// 清理资源
}
五、关键接口与类
- Servlet:核心接口,定义了
init()
、service()
和destroy()
方法。 - Filter:用于拦截请求和响应。
- Listener:用于监听应用生命周期事件。
- ServletContext:全局上下文对象,存储共享数据和资源。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步