JavaWeb——Servlet生命周期
一、什么是 Servlet 生命周期?
Servlet 生命周期指的是一个 Servlet 对象从被创建、初始化、接收请求、处理请求,到最后被销毁的整个过程。这个过程由 Web 容器(如 Tomcat, Jetty) 全权管理。开发者不需要手动创建 new MyServlet()
,容器会根据规则在合适的时机调用生命周期方法。
二、阶段一:初始化 (init
)
目的:在 Servlet 开始处理客户端请求之前,进行一些一次性的、昂贵的资源加载工作(例如建立数据库连接、读取配置文件等)。
触发时机(两种):
默认情况(延迟加载):当容器启动后,Servlet 并不会立即被初始化。只有当 第一个请求 到达这个 Servlet 时,容器才会创建其实例并初始化。
启动时加载(积极加载):在
web.xml
中配置<load-on-startup>
参数,可以让容器在 Web 应用启动时就创建和初始化 Servlet。数值越小,启动优先级越高。参数冲突时,Tomcat会自动处理。
<servlet><servlet-name>MyServlet</servlet-name><servlet-class>com.example.MyServlet</servlet-class><load-on-startup>1</load-on-startup> <!-- 数字1表示高优先级 -->
</servlet>
执行方法:init(ServletConfig config)
容器会调用这个方法。它只会被调用一次,在 Servlet 的整个生命周期中绝不会被调用第二次。
ServletConfig
对象包含了 Servlet 的初始化参数(在web.xml
中配置)和 Servlet 上下文(ServletContext
)的引用。通常,我们不会直接重写此方法,而是重写没有参数的
init()
方法。因为GenericServlet
类已经提供了一个无参init()
方法,它会在参数版的init(ServletConfig)
方法中被调用。
代码示例:
public class MyServlet extends HttpServlet {private Connection dbConnection;@Overridepublic void init() throws ServletException {// 这个方法在 init(ServletConfig) 中被调用,只执行一次try {// 执行一次性的初始化,比如建立数据库连接Class.forName("com.mysql.cj.jdbc.Driver");dbConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");System.out.println("数据库连接在init()中建立");} catch (Exception e) {throw new ServletException("初始化数据库连接失败", e);}}
}
特点:
init
方法执行完毕后,Servlet 就处于 “已就绪” 状态,随时可以处理客户端的请求。
三、阶段二:处理请求 (service
)
目的:处理所有到达该 Servlet 的客户端请求并返回响应。
触发时机:每一次客户端请求到达该 Servlet 时,容器都会调用 service
方法。
执行方法:service(ServletRequest request, ServletResponse response)
对于
HttpServlet
,我们通常不需要重写这个方法。HttpServlet
类已经为我们实现了标准的service
方法。它会根据请求的 HTTP 方法(GET, POST, PUT, DELETE 等),自动调用相应的doGet()
,doPost()
,doPut()
,doDelete()
等方法。我们的主要工作就是重写那些
doXxx
方法来实现业务逻辑。
代码示例:
public class MyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 处理GET请求(如点击链接,直接访问URL)response.getWriter().println("处理了一个GET请求");// 可以使用在init中初始化的dbConnection}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 处理POST请求(如表单提交)String user = request.getParameter("user");response.getWriter().println("你好, " + user + "! 这是一个POST请求。");}
}
特点:
service
方法(及其分支doGet
/doPost
)在 Servlet 的生命周期中可以被调用无数次。容器会为每个请求创建一个新的请求对象(HttpServletRequest) 和响应对象(HttpServletResponse),然后开启一个新的线程来执行
service
方法。因此,Servlet 实例是单例多线程的。
四、阶段三:销毁 (destroy
)
目的:在 Servlet 生命周期结束时,释放它所占用的资源(例如关闭数据库连接、清理内存等)。
触发时机:
Web 应用被卸载(或重新加载)。
Web 服务器被关闭。
容器检测到需要重新加载 Servlet(某些容器的开发模式)。
执行方法:destroy()
这个方法也只会被调用一次,在 Servlet 生命周期结束时。
在
destroy()
方法调用之后,容器会释放这个 Servlet 实例,随后它会被垃圾回收器回收。容器会在调用
destroy()
之前,确保所有正在service
方法中执行的线程都已结束或超时。
代码示例:
public class MyServlet extends HttpServlet {private Connection dbConnection;// ... init() 和 doGet/doPost 方法 ...@Overridepublic void destroy() {// 释放资源try {if (dbConnection != null && !dbConnection.isClosed()) {dbConnection.close();System.out.println("数据库连接在destroy()中关闭");}} catch (SQLException e) {e.printStackTrace();}}
}
五、要点
单例多线程:
一个 Servlet 类在容器中通常只有一个实例。对于并发请求,容器会使用多个线程,每个线程执行同一个 Servlet 实例的 service
方法。因此,要特别注意线程安全问题。记住,不要使用实例变量来存储请求相关的数据,因为它们会被所有线程共享。但是可以使用局部变量,它们是线程安全的。