Servlet--生命周期

现在开始正式的进入Servlet的编码整理,首当其冲的就是Servlet的生命周期。

  • Servlet的生命周期一共分为5个部分,加载程序,初始化,服务,销毁,卸载。

1,加载程序(只调用一次):其实也就是实例化,web容器负责加载Servlet,当容器启动时或者第一次使用这个Servlet的时候,容器会负责创建这个Servlet实例,但是用户必须通过web.xml配置文件来指定Servlet的位置,这个特别要注意,这个位置必须是包名+类名。
那么问题来了?为什么必须要加上包名呢?
很简单:如果不加包名,要是有一个类名一样的类就没法区分了。然后还有就是web容器成功加载这个配置文件后,肯定是通过反射来对这个Servlet实例化的,所以这里的写位置的时候就必须是包名+类名,并且必须是包.类名称这种格式。

2,初始化(只调用一次):当一个Servlet被实例化后,容器将调用init()或者init(ServletConfig servletConfig)来初始化这个对象,初始化的目的是为了让这个Servlet对象在处理客户端请求前完成一些初始化的工作,比如说建立数据库链接啦,读取资源文件信息啦等等,如果初始化失败,则此Servlet将被直接卸载。
问题来了?为什么我前面说容器调用init()或者init(ServletConfig servletConfig)这里有2个方法呢?
很简单:我们在自己编码Servlet的时候要不就是实现Servlet接口,要不就是继承httpServlet抽象类,在Servlet中使用init()方法来初始化,在httpServlet中,其实是继承于GenericServlet类,使用init(ServletConfig servletConfig)方法来初始化的。

3,处理服务(可调用多次)
当有请求提交时,Servlet将调用service()方法来进行处理,在service方法中,Servlet通过ServletRequest接收客户的请求,也可以利用ServletResponse设置相应信息。处理一次请求,返回一次响应,调用一次这个方法。

4,销毁(只调用一次)
当web容器关闭或者检测到一个Servlet要从容器中被删除的时候,会自动调用destroy()方法,以便让实例释放掉被占用的资源。

5,卸载(只调用一次)
当一个Servlet销毁后,此实例将等待被垃圾收集器回收,如果需要再次使用此Servlet,会重新调用init()方法初始化。这里有一点要注意:在正常情况下,Servlet只会初始化一次,而处理服务会调用多次,销毁也只会调用一次。但是如果一个Servlet长时间不适用的话,也会被容器自动销毁,而如果需要再次使用时会重新进行初始化的操作,即在特殊情况下初始化可能会进行多次,销毁也会进行多次。到底什么时候是特殊情况呢? 一会下面再说这个。

具体如下图所示:
Servlet--生命周期

下面自己写一个Servlet来研究下Servlet的生命周期:
package linkin;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class LinkinServlet implements Servlet
{

	public LinkinServlet()
	{
		System.out.println("LinkinServlet...");
	}

	@Override
	public void destroy()
	{
		System.out.println("destroy...");
	}

	@Override
	public ServletConfig getServletConfig()
	{
		return null;
	}

	@Override
	public String getServletInfo()
	{
		return null;
	}

	@Override
	public void init(ServletConfig servletConfig) throws ServletException
	{
		System.out.println("init...");
	}

	@Override
	public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException
	{
		System.out.println("service...");
	}


}

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<servlet>
		<servlet-name>LinkinServlet</servlet-name>
		<servlet-class>linkin.LinkinServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>LinkinServlet</servlet-name>
		<url-pattern>/LinkinServlet</url-pattern>
	</servlet-mapping>

</web-app>

Servlet--生命周期Servlet--生命周期


写完上面的代码,基本就可以比较细致的了解了Servlet的生命周期了,但是这里会不由得引发出3个问题:
1,平时我们自己写代码的时候,初始化一个对象实例,都是去调用他的构造器然后自己new实例出来,现在Servlet帮我们做了,我们在使用Servlet的时候就可以直接用这个Servlet了呢。但是这个初始化时机是什么时候呢?上面的代码我们看到是在第一次处理请求的时候Servlet对象被初始化,要是我现在想在启动tomcat的时候就初始化这个Servlet对象可以做到么?其实这种情节很常见的,比如说我们要在启动容器的时候读取一些静态文件等等。

解决上面的问题,有必要来研究一个web.xml中的一个标签:load-on-startup。

load-on-startup,配置在servlet节点中,用来指定Servlet被创建的时机,若为负数,则在第一次请求时被创建,若为0或者正数,则在当前Servlet容器加载时创建实例,数值越小,载入的优先级越高,越早创建。优先级提倡从1开始,1以下的数字,有些容器不理会。负数则被认为是“默认”。如果没有配置,则在第一次请求时才实例化。


2,很明显的我们知道同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。但是要是多次写映射是不是有点土呢?有时候我们要用甚至不想写具体的映射只想写统配,应该怎么写呢?

一定要注意:在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾,比如“/action/*”。


3,关于Servlet的销毁前面也说到了,当容器关闭或者长时间不使用的时候回自动进行销毁,而如果容器配置了动态加载(reloadable="true"),每当重新加载新的内容后,实际上Servlet也会销毁。如果观察不到销毁信息,可以在destroy()方法中加入一个线程的延迟(Thread.sleep(3000))操作用来延迟容器的关闭时间。

Servlet--生命周期