作为一名专业编程人员,您碰到的大多数 Java servlet 都是为响应 Web 应用程序上下文中的 HTTP 请求而设计的。因此,javax.servlet 和 javax.servlet.http 包中特定于 HTTP 的类是您应该关心的。对于Servlet容器(Tomcat)与HttpServlet是怎样进行交互的呢,看下类图:
Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。在Servlet接口中定义了5个方法,其中3个方法代表了Servlet的生命周期:
- init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;
- service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性;
- destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源;
编程注意事项说明:
- 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。
- 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。
- 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:
如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。
如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。
如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。
如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。
如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。
在创建一个 Java servlet 时,一般需要子类 HttpServlet。该类中的方法允许您访问请求和响应包装器(wrapper),您可以用这个包装器来处理请求和创建响应。大多数程序员都知道Servlet的生命周期,简单的概括这就分为四步:
Servlet类加载--->实例化--->服务--->销毁;
创建Servlet对象的时机:
- 默认情况下,在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet对象的service方法;
- Servlet容器启动时:当web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素时,Servlet容器在启动web服务器时,将按照顺序创建并初始化Servlet对象;
- Servlet的类文件被更新后,重新创建Servlet。Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的<load-on-startup>属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在;
注意:在web.xml文件中,某些Servlet只有
<serlvet>
元素,没有<servlet-mapping>
元素,这样我们无法通过url的方式访问这些Servlet,这种Servlet通常会在<servlet>
元素中配置一个<load-on-startup>
子元素,让容器在启动的时候自动加载这些Servlet并调用init(ServletConfig config)方法来初始化该Servlet。其中方法参数config中包含了Servlet的配置信息,比如初始化参数,该对象由服务器创建。
销毁Servlet对象的时机:
Servlet容器停止或者重新启动:Servlet容器调用Servlet对象的destroy方法来释放资源。以上所讲的就是Servlet对象的生命周期。那么Servlet容器如何知道创建哪一个Servlet对象?Servlet对象如何配置?实际上这些信息是通过读取web.xml配置文件来实现的。
<servlet> <!-- Servlet对象的名称 --> <servlet-name>action<servlet-name> <!-- 创建Servlet对象所要调用的类 --> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <!-- 参数名称 --> <param-name>config</param-name> <!-- 参数值 --> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <!-- Servlet容器启动时加载Servlet对象的顺序 --> <load-on-startup>2</load-on-startup> </servlet> <!-- 要与servlet中的servlet-name配置节内容对应 --> <servlet-mapping> <servlet-name>action</servlet-name> <!-- 客户访问的Servlet的相对URL路径 --> <url-pattern>*.do</url-pattern> </servlet-mapping>
当Servlet容器启动的时候读取<servlet>配置节信息,根据<servlet-class>配置节信息创建Servlet对象,同时根据<init-param>配置节信息创建HttpServletConfig对象,然后执行Servlet对象的init方法,并且根据<load-on-startup>配置节信息来决定创建Servlet对象的顺序,如果此配置节信息为负数或者没有配置,那么在Servlet容器启动时,将不加载此Servlet对象。当客户访问Servlet容器时,Servlet容器根据客户访问的URL地址,通过<servlet-mapping>配置节中的<url-pattern>配置节信息找到指定的Servlet对象,并调用此Servlet对象的service方法。
在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。下面给出Servlet整个生命周期过程的UML序列图,如图所示:
如果需要让Servlet容器在启动时即加载Servlet,可以在web.xml文件中配置<load-on-startup>元素。
7.2 Servlet工作原理##
上面描述了Servlet的生命周期,接着我们描述一下Tomcat与Servlet是如何工作的,首先看下面的时序图:
- Web Client 向Servlet容器(Tomcat)发出Http请求;
- Servlet容器接收Web Client的请求;
- Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中;
- Servlet容器创建一个HttpResponse对象;
- Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象;
- HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息;
- HttpServlet调用HttpResponse对象的有关方法,生成响应数据;
- Servlet容器把HttpServlet的响应结果传给Web Client;