Servlet生命周期全解析 - 教程

Servlet 生命周期详解

一、Servlet 生命周期概述

Servlet 生命周期是 Java Web 开发的核心概念,指的是从 Servlet 被创建到被销毁的完整过程,由 Servlet 容器(如 Tomcat、Jetty 等)严格管理。理解 Servlet 生命周期对于开发高效、稳定的 Web 应用至关重要,它决定了 Servlet 如何初始化资源、处理请求以及释放资源。

Servlet 生命周期主要包含五个核心阶段:加载与实例化、初始化、服务就绪、请求处理和销毁。每个阶段都有明确的触发条件和执行逻辑,容器会按照预定的顺序调用相应的方法。

二、加载与实例化阶段

触发时机
  • 默认行为:当 Servlet 第一次被请求时
  • 配置行为:通过loadOnStartup属性配置为服务器启动时加载
执行过程
  1. 类加载:Servlet 容器通过类加载器加载 Servlet 类文件
  2. 实例化:容器调用 Servlet 的无参构造函数创建实例
关键特性
  • 每个 Servlet 类通常只有一个实例(单例模式)
  • 所有客户端请求共享同一个实例
  • 线程安全成为核心问题
代码示例

java

运行

public class MyServlet extends HttpServlet {
    /**
     * 默认构造器 - 由Servlet容器调用
     */
    public MyServlet() {
        System.out.println("Servlet实例初始化完成");
    }
}

三、初始化阶段

触发时机
  • 实例化后立即执行
  • 整个生命周期中只执行一次
执行过程
  1. 容器创建ServletConfig对象
  2. 调用init(ServletConfig config)方法
关键方法

java

运行

@Override
public void init(ServletConfig config) throws ServletException {
    super.init(config); // 必须调用父类初始化方法
    // 获取初始化参数
    String dbUrl = config.getInitParameter("databaseURL");
    String userName = config.getInitParameter("userName");
    // 初始化数据库连接池
    connectionPool = createConnectionPool(dbUrl, userName);
    System.out.println("Servlet初始化完成");
}
初始化参数配置

通过 web.xml 配置:

xml


    MyServlet
    com.example.MyServlet
    
        databaseURL
        jdbc:mysql://localhost:3306/mydb
    
    
        userName
        root
    
    1

通过注解配置:

java

运行

@WebServlet(
    name = "MyServlet",
    urlPatterns = {"/myServlet"},
    initParams = {
        @WebInitParam(name = "databaseURL", value = "jdbc:mysql://localhost:3306/mydb"),
        @WebInitParam(name = "userName", value = "root")
    },
    loadOnStartup = 1
)
最佳实践
  • 资源初始化:数据库连接池、线程池、缓存等
  • 加载配置文件
  • 执行一次性计算
  • 避免耗时操作(会延迟应用启动)

四、服务就绪阶段

状态特征
  • Servlet 完成初始化
  • 处于待命状态,等待客户端请求
  • 可以立即处理请求
技术实现
  • 容器维护 Servlet 实例在内存中
  • 准备处理请求的线程池
  • 建立请求 / 响应对象池(优化性能)

五、请求处理阶段

触发时机
  • 每当有新的客户端请求被映射到该 Servlet 时
  • 可以执行多次(每次请求都会调用)
核心流程
  1. 容器接收 HTTP 请求
  2. 创建或分配线程处理请求
  3. 创建HttpServletRequestHttpServletResponse对象
  4. 调用 Servlet 的service()方法
  5. 根据 HTTP 方法路由到具体的doXxx()方法
  6. 生成响应并返回客户端
  7. 容器回收请求 / 响应对象和线程
service () 方法实现

java

运行

/**
 * 处理HTTP请求的核心服务方法
 */
public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {
    // 转换为HTTP特定的请求/响应对象
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    // 根据HTTP方法进行路由分发
    String method = request.getMethod();
    switch (method) {
        case "GET":
            doGet(request, response);
            break;
        case "POST":
            doPost(request, response);
            break;
        case "PUT":
            doPut(request, response);
            break;
        case "DELETE":
            doDelete(request, response);
            break;
        // 其他HTTP方法处理
    }
}
doXxx () 方法实现

java

运行

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");
    // 获取请求参数
    String name = request.getParameter("name");
    String age = request.getParameter("age");
    // 执行业务逻辑
    String greeting = "Hello, " + (name != null ? name : "Guest");
    // 生成响应
    PrintWriter out = response.getWriter();
    out.println("");
    out.println("Servlet Example");
    out.println("");
    out.println("

" + greeting + "

"); out.println("

Age: " + (age != null ? age : "Not provided") + "

"); out.println(""); out.println(""); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 处理POST请求的逻辑 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); // 验证用户信息 boolean isValid = userService.validateUser(username, password); if (isValid) { // 登录成功 HttpSession session = request.getSession(); session.setAttribute("username", username); response.sendRedirect("welcome.jsp"); } else { // 登录失败 request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }
关键特性
  • 单实例多线程模型:每个请求在独立线程中执行
  • 线程安全挑战service()doXxx()方法可被并发调用
  • 请求 / 响应对象隔离:每个请求都有独立的对象实例

六、销毁阶段

触发时机
  • Web 应用停止(服务器关闭)
  • Web 应用重新部署
  • Servlet 被动态卸载
执行过程
  1. 容器停止接受新请求
  2. 等待所有正在处理的请求完成(可配置超时)
  3. 调用destroy()方法
  4. 销毁 Servlet 实例
  5. 执行垃圾回收
关键方法

java

运行

@Override
public void destroy() {
    // 释放数据库连接池资源
    if (connectionPool != null) {
        connectionPool.close();
        System.out.println("数据库连接池已关闭");
    }
    // 停止后台线程
    if (backgroundThread != null && backgroundThread.isRunning()) {
        backgroundThread.stop();
        System.out.println("后台线程已停止");
    }
    // 保存应用状态
    saveApplicationState();
    System.out.println("Servlet资源释放完成");
}
最佳实践
  • 关闭数据库连接
  • 停止后台线程
  • 释放文件句柄
  • 保存应用状态
  • 记录日志信息

七、关键特性深度解析

单例与线程安全

实现机制

java

运行

public class ServletContainer {
    // 缓存Servlet实例
    private final Map servletCache = new ConcurrentHashMap<>();
    public Servlet getServlet(String servletName) {
        // 双重检查锁定实现线程安全
        if (!servletCache.containsKey(servletName)) {
            synchronized (this) {
                if (!servletCache.containsKey(servletName)) {
                    Servlet servlet = createServlet(servletName);
                    servlet.init(config);
                    servletCache.put(servletName, servlet);
                }
            }
        }
        return servletCache.get(servletName);
    }
}

线程安全问题

java

运行

// 不安全场景
public class UnsafeServlet extends HttpServlet {
    // 实例变量 - 线程不安全
    private int counter = 0;
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 多个线程同时修改counter变量
        counter++;
        response.getWriter().write("Counter: " + counter);
    }
}

解决方案

  1. 使用局部变量(推荐)
  2. 使用线程安全集合(如 ConcurrentHashMap)
  3. 添加同步机制(如 synchronized)
  4. 使用 ThreadLocal
ServletConfig 与 ServletContext 对比
特性ServletConfigServletContext
作用范围当前 Servlet整个 Web 应用
获取方式getServletConfig()getServletContext()
参数配置<init-param><context-param>
生命周期与 Servlet 一致与 Web 应用一致
loadOnStartup 配置
  • 正数:服务器启动时加载,数值越小优先级越高
  • 0:服务器启动时加载,优先级最低
  • 负数:默认行为,首次请求时加载

八、性能优化与最佳实践

资源管理优化

连接池管理

java

运行

private DataSource dataSource;
@Override
public void init() throws ServletException {
    super.init();
    // 初始化连接池
    BasicDataSource ds = new BasicDataSource();
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    ds.setUrl(getServletConfig().getInitParameter("databaseURL"));
    ds.setUsername(getServletConfig().getInitParameter("userName"));
    ds.setPassword(getServletConfig().getInitParameter("password"));
    // 连接池配置
    ds.setInitialSize(5);
    ds.setMaxActive(20);
    ds.setMaxIdle(10);
    ds.setMinIdle(2);
    this.dataSource = ds;
}
线程安全优化

避免实例变量

java

运行

// 正确做法 - 使用局部变量
public class GoodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        User currentUser = getUserFromRequest(request);  // 局部变量
        processUser(currentUser);  // 线程安全
    }
}
缓存策略

页面缓存

java

运行

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // 设置缓存控制头
    response.setHeader("Cache-Control", "public, max-age=3600");
    response.setHeader("Expires", new Date(System.currentTimeMillis() + 3600000).toString());
    // 检查缓存是否存在
    String cacheKey = "page_" + request.getRequestURI();
    String cachedContent = cacheService.get(cacheKey);
    if (cachedContent != null) {
        // 使用缓存内容
        response.getWriter().write(cachedContent);
        return;
    }
    // 生成页面内容
    String content = generatePageContent();
    // 缓存页面内容
    cacheService.put(cacheKey, content, 3600);
    response.getWriter().write(content);
}
异步处理

异步 Servlet 配置

java

运行

@WebServlet(
    urlPatterns = "/async",
    asyncSupported = true  // 启用异步支持
)
public class AsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 开启异步处理
        AsyncContext asyncContext = request.startAsync();
        // 设置超时时间
        asyncContext.setTimeout(30000);
        // 异步处理任务
        executorService.submit(() -> {
            try {
                // 执行耗时操作
                String result = longRunningOperation();
                // 完成异步处理
                asyncContext.getResponse().getWriter().write(result);
                asyncContext.complete();
            } catch (Exception e) {
                asyncContext.complete();
            }
        });
    }
}

九、常见问题与解决方案

内存泄漏问题

常见原因

  1. 未关闭资源:数据库连接、文件流等
  2. 线程泄漏:创建的线程未正确停止
  3. 类加载器泄漏:静态变量引用导致类无法卸载

解决方案

java

运行

@Override
public void destroy() {
    // 关闭数据库连接池
    if (dataSource != null) {
        try {
            ((BasicDataSource) dataSource).close();
        } catch (SQLException e) {
            log.error("Failed to close data source", e);
        }
    }
    // 停止线程池
    if (executorService != null) {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
    }
    // 清除静态引用
    if (staticCache != null) {
        staticCache.clear();
    }
}
线程安全问题

问题诊断

java

运行

// 使用ThreadLocal跟踪请求信息
private ThreadLocal requestContext = new ThreadLocal<>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    RequestContext context = new RequestContext();
    context.setRequestId(UUID.randomUUID().toString());
    context.setStartTime(System.currentTimeMillis());
    requestContext.set(context);
    try {
        // 业务处理
        processRequest(request, response);
    } finally {
        // 清除ThreadLocal,避免内存泄漏
        requestContext.remove();
    }
}

十、总结

Servlet 生命周期是 Java Web 开发的核心概念,理解其工作原理对于构建高性能、稳定的 Web 应用至关重要。通过掌握 Servlet 的加载、初始化、服务和销毁等各个阶段,开发者可以更好地管理资源、处理并发请求,并避免常见的性能和安全问题。

在实际开发中,我们应该:

  1. 合理管理资源:在 init () 方法中初始化资源,在 destroy () 方法中释放资源
  2. 确保线程安全:避免使用实例变量,使用线程安全的数据结构
  3. 优化性能:使用连接池、缓存等技术提高系统性能
  4. 处理异常:完善的异常处理机制保证系统稳定性
  5. 监控与调优:通过监控工具及时发现和解决问题

随着 Java Web 技术的发展,虽然出现了 Spring MVC 等更高级的框架,但 Servlet 作为 Java Web 的基础技术,仍然是每个 Java 开发者必须掌握的核心知识。理解 Servlet 生命周期不仅有助于我们更好地使用这些高级框架,也为我们解决复杂的 Web 开发问题提供了基础理论支持。

posted @ 2026-01-18 14:50  yangykaifa  阅读(2)  评论(0)    收藏  举报