1.什么是Servlet?
Servlet是一个Java编写的程序,此程序是在服务器端运行的,是按照Servlet规范编写的一个Java类。
2.Servlet是做什么的?
Servlet是处理客户端的请求并将其发送到客户端。
3.Servlet API.
3.1Servlet框架的组成
Servlet框架是由2个Java包组成:(1)javax.servlet和(2)javax.servlet.http
(1)javax.servlet包中定义了所有Servlet类都必须实现的接口和类。
javax.servlet包的接口为:
ServletConfig接口 ---- 在初始化过程中由Servlet容器使用
ServletContext接口 ---- 定义Servlet用于获取容器信息的方法
ServletRequest接口 ---- 向服务器请求信息
ServletResponse接口 ---- 响应客户端请求
Servlet接口 ---- 定义所有Servlet必须实现的方法
javax.servlet包中的类为:
ServletInputStream类 ---- 用于从客户端读取二进制数据
ServletOutputStream类 ---- 用于将二进制数据发送到客户端
GenericServlet类 ---- 抽象类,定义一个通用的,独立于地层协议的Servlet。
(2)javax.servlet.http包中定义了采用HTTP通信的HttpServlet类。
javax.servlet.http包的接口为:
HttpServletRequest接口 ---- 提供http请求
HttpServletResponse接口 ---- 提供http响应
HttpSession接口 ---- 用于标识客户端并存储有关客户信息
HttpSessionAttributeListener接口 ----实现这个侦听接口用户获取会话的属性列表的改变的通知
javax.servlet.http包中的类:
HttpServlet类 ---- 扩展了 GenericServlet的抽象类,用于扩展创建Http Servlet
Cookie类 ---- 创建一个Cookie,用于存储Servlet发送给客户端的信息
3.2Servlet框架的核心
Servlet框架的核心是:javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口。
在这个类中定义了5个方法,其中有三个方法是由Servlet容器来调用,容器会在Servlet生命周期不同阶段调用特定的方法。
(1)init(ServletConfig config)方法:负责初始化Servlet对象。容器在创建好Servlet对象后,就回调用这个方法;
(2)service(ServleteRequest req,ServletRequest res)方法,负责响应客户的请求,为客户提供相应的服务。当容器收到客户端要求访问特定Servlet请求时,就会调用该Sevlet对象的service()方法;
(3)destroy()方法:负责释放Servlet对象所占有的资源。Servlet生命周期结束时,会调用这个方法。
3.3HttpServlet抽象类
HttpServlet类(我们大部分自定义的Servlet都是继承这个类)是GenericServlet类(实现了Servlet接口,现在很少直接用)的子类。HttpServlet类与Servlet接口提供了与HTTP协议相关的通用实现。
4.Sevlet的工作流程
1) 客户端向Web服务器发送请求;
2) 服务器将请求信息发送至 Servlet;
3) Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求;
4) 服务器将响应返回给客户端。
5.Servlet的工作原理
2)每一个自定义的Servlet都必须实现Servlet的接口,Servlet接口中定义了五个方法,其中比较重要的三个方法涉及到Servlet的生命周期,分别是上文提到的init(),service(),destroy()方法。GenericServlet是一个通用的,不特定于任何协议的Servlet,它实现了Servlet接口。而HttpServlet继承于GenericServlet,因此HttpServlet也实现了Servlet接口。所以我们定义Servlet的时候只需要继承HttpServlet即可。
3)Servlet接口和GenericServlet是不特定于任何协议的,而HttpServlet是特定于HTTP协议的类,所以HttpServlet中实现了service()方法,并将请求ServletRequest、ServletResponse 强转为HttpRequest 和 HttpResponse。
步骤:
(1)Web Client 向Servlet容器(Tomcat)发出Http请求;
(2)Servlet容器接收Web Client的请求;
(3)Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中;
(4)Servlet容器创建一个HttpResponse对象;
(5)Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet 对象;
(6)HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息;
(7)HttpServlet调用HttpResponse对象的有关方法,生成响应数据;
(8)Servlet容器把HttpServlet的响应结果传给Web Client。
6.Servlet的生命周期
(1)Servlet运行在Servlet的容器中,其生命周期有容器进行管理,通过javax.servlet.Servlet接口的init()、service()、destory()方法来实现;
(2)Servlet的生命周期包含下面几个阶段:
1)加载和实例化
Servlet容器负责Servlet的加载和实例化,当Servlet容器启动时,或者Servlet容器检测到需要这个Servlet服务的第一个请求时,Servlet会加载这个Servlet,并生成Servlet实例。(注:servlet容器在启动后,必须知道这些servlet类所在的位置,servlet容器可以通过本地文件系统、远程文件系统或者其他网络服务中,通过类加载器加载servlet类,加载成功后才能创建servlet实例;servlet容器通过java的反射API,来进行实例化,容器调用的是servlet的无参构造方法-即默认的构造方法,所以我们在编写servlet类时,不要写带参的构造方法)
创建Servlet对象的时机:
(1)Servlet容器启动时:读取web.xml配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对 象,同时将ServletConfig对象作为参数来调用Servlet对象的init方法。
(2)在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servle t对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet 对象的service方法。
(3)Servlet Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的<load-on-st artup>属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在。
<servlet>
<servlet-name>Init</servlet-name>
<servlet-class>org.xl.servlet.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
2)初始化
当servlet实例化后,容器将调用这个对象的init()方法进行初始化,初始化的目的是在这个实例为请求提供服务前完成初始化工作,如建立配置连接,获取配置信息等。每个servlet实例,容器只调用一次init()方法。servlet实例可以使用容器为其提供的ServletConfig对象,从web应用程序的配置信息中(即web.xml文件),获取初始化的参数信息。如果初始化期间发生错误,则会抛出ServletException异常或者unavailableException异常,来通知容器ServletException异常是一般的初始化失败,如没能找到初始化参数;UnavailableException异常是通知容器此实例不可用,如数据库未启动,数据连接不成功,servlet就会向容器抛出UnvailableException异常,提示他暂时或永久不可用。在web.xml中该Servlet的定义标记中,比如:
<servlet>
<servlet-name>TimeServlet</servlet-name>
<servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
<init-param>
<param-name>user</param-name>
<param-value>username</param-value>
</init-param>
<init-param>
<param-name>blog</param-name>
<param-value>http://...</param-value>
</init-param>
</servlet>
配置了两个初始化参数user和blog它们的值分别为username和http://..., 这样以后要修改用户名和博客的地址不需要修改Servlet代码,只需修改配置文件即可。
II.如何读取Servlet的初始化参数?
ServletConfig中定义了如下的方法用来读取初始化参数的信息:
public String getInitParameter(String name)
参数:初始化参数的名称。
返回:初始化参数的值,如果没有配置,返回null。
III.init(ServletConfig)方法执行次数
在Servlet的生命周期中,该方法执行一次。
IV.init(ServletConfig)方法与线程
该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题。
V.init(ServletConfig)方法与异常
该方法在执行过程中可以抛出ServletException来通知Web服务器Servlet实例初始化失败。一旦ServletException 抛出,Web服务器不会将客户端请求交给该Servlet实例来处理,而是报告初始化失败异常信息给客户端,该Servlet实 例将被从内存中销毁。如果在来新的请求,Web服务器会创建新的Servlet实例,并执行新实例的初始化操作
3)请求处理(服务)
Servlet容器调用servlet实例的service()方法来对请求进行处理。需要强调的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,servlet实例通过ServletRequest对象,来获取客户端的相关信息和请求信息;处理完成后,servlet实例通过ServletResponse对象来设置相应信息。service()方法自动运行与请求对应的doXXX方法,如果请求是get方式的,则调用doGet()方法;如果请求是post方式的,则调用doPost()方法。当处理过程中出现异常,则servlet实例会抛出ServletException异常或者UnavailableException异常。如果抛出的UnvailableException异常显示此实例永久不可用,则容器将调用servlet实例的destroy()方法,释放该实例,释放后对此实例的所有请求,都将得到HTTP404的响应(请求的资源不可用);如果抛出的UnavailableExceptio异常显示此实例暂时不可用,则在暂时不可用的时间段内,对此实例的请求,都将得到HTTP503的响应(服务暂时忙,不能处理请求)。
一旦Servlet实例成功创建及初始化,该Servlet实例就可以被服务器用来服务于客户端的请求并生成响应。在服务阶段Web服务器会调用该实例的service(ServletRequest request, ServletResponse response)方法,request对象和response对象是服务器创建并传给Servlet实例。request对象封装了客户端发往服务器端的信息,response对象封装了服务器发往客户端的信息。
I. service()方法的职责
service()方法为Servlet的核心方法,客户端的业务逻辑应该在该方法内执行,典型的服务方法的开发流程为:
解析客户端请求-〉执行业务逻辑-〉输出响应页面到客户端
II.service()方法与线程
为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性。
III.service()方法与异常
service()方法在执行的过程中可以抛出ServletException和IOException。其中ServletException可以在处理客户端请求的过程中抛出,比如请求的资源不可用、数据库不可用等。一旦该异常抛出,容器必须回收请求对象,并报告客户端该异常信息。IOException表示输入输出的错误,编程者不必关心该异常,直接由容器报告给客户端即可。
1) 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。
2) 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。
3) 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:
i.如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。
ii.如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。
iii.如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。
iv.如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。
v.如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。
4)服务结束(实例销毁)
当容器检测到某个Servlet实例没有存在的必要了,比如应用重新装载,或服务器关闭,以及Servlet很长时间都没有被访问过。容器可以从内存中销毁(也叫卸载)该实例。Web服务器必须保证在卸载Servlet实例之前调用该实例的destroy()方法,以便回收Servlet申请的资源或进行其它的重要的处理。Web服务器必须保证调用destroy()方法之前,让所有正在运行在该实例的service()方法中的线程退出或者等待这些线程一段时间。一旦destroy()方法已经执行,Web服务器将拒绝所有的新到来的对该Servlet实例的请求,destroy()方法退出,该Servlet实例例随后将由垃圾回收器进行垃圾回收处理。如果再有对此实例的服务请求时,容器将重新创建一个新的servlet实例。 参考:Servlet Servlset的生命周期 Setvlet的执行原理和生命周期