servlet基础(组成与生命周期)

时间:2021-05-27 02:48:13

servlet基础
作用:servlet是运行在Web服务器或应用服务器上的程序;担当web浏览器或其他HTTP客户程序发出的请求与HTTP服务器上的数据库或应用程序之间的中间层。
1、读取客户程序发送的显式数据
2、读取浏览器发出的隐式HTTP请求数据
HTTP信息包括cookie、媒体信息和浏览器能够识别的压缩模式
3、生成结果
可能与数据库对方,调用web服务或者直接计算得到响应
4、向客户发送显式数据(即文档)
这种文档可以用各种不同的形式发送,包括文本、二进制、exce,甚至是其他压缩格式
5、发送隐式的HTTP响应数据
发送到用户的数据有两种:文档本身、以及后台的HTTP信息。两种类型的数据对开发工作都至关重要。发送HTTP响应数据时,需要告知浏览器或其他客户程序返回文档的类型(如HTML)、设置cookie并缓存参数、以及其他类似任务。
servlet的基本结构:
servlet一般扩展HttpServlet,并依据数据发送方式的不同(GET或POST),覆盖doGet或doPost方法。如果希望servlet对GET和POST请求采取同样的行动,只需要让doGet调用doPost,反之亦然。
doGet和doPost接受两个参数:HttpServletRequest和HttpServletResponse

生成HTML的servlet
大多数servlet生成HTML,而并非前面例子中的纯文本。要生成HTML,需要在刚才介绍的过程中加入下面3步:
1、告知浏览器,即将向他发送HTML
使用HTTP Content-Type响应报头设置为text/html来完成
必须在传送实际的文档之前设定内容的类型
2、修改println语句,构建合法的Web页面
3、验证生成的HTML语法

servlet的生命周期:
服务器只创建每个servlet的单一实例,每个用户请求都会引发新的线程。现在,更加详细的介绍servlet的创建和销毁方式,以及调用各种方法的方式和时间。
首次创建servlet时,它的init方法会得到调用。在这之后,针对每个用户请求,都会创建一个线程,该线程调用前面创建的实例的service方法。多个并发请求一般会导致多个线程共同调用service(尽管可以实现特殊的接口SingleThreadModel——规定任何时间只允许单个线程运行)。之后,由service方法依据收到的Http请求类型,调用对应的doGet等方法。最后,如果服务器决定卸载某个servlet,它会首先调用servlet的destory方法。

service方法:
服务器每次接受到对servlet的请求,都会产生一个新的线程,调用service方法。service方法检查HTTP请求的类型并相应地调用doGet等方法。
如果您需要在servlet中等同的处理POST和GET请求,您可能会倾向于不去实现doGet和doPost方法,而是直接覆盖service方法。这不是一个好思想。取而代之,只需让doGet调用doPost(或相反)即可。
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//Servlet code
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
虽然这种方式要多出几行代码,但相对于直接覆盖service,它有好几项优点。首先,之后您还可以加入doPut,doTrace等,支持其他HTTP请求方法。其次,您还可以通过添加getLastModified方法,加入对修改日期的支持。由于getLastModified由默认的service方法调用,所以覆盖service方法也失去了这个选项。
doGet、doPost、doXX方法:
这些方法才是servlet的主体
init方法:
大多时候,您的servlet只需要处理单个请求的数据,doGet或doPost是成名周期中唯一需要的方法。然而,有时候您希望在servlet首次载入的时候执行复杂的初始化任务,并且不想每个请求都重复这些任务。init方法就专门为这种情况设计,它在servlet初次创建时被调用,之后处理每个用户的请求时,则不再调用这个方法。
servlet一般在用户首次调用对应servlet的URL时创建,也可以制定和servlet在服务器启动后载入。
init方法的定义:
public void init() throws ServletException {
//Initialization code...
}
init方法执行两种类型的初始化:常规初始化,以及由初始化参数控制的初始化。
常规初始化:
第一种类型的初始化中,init只是创建或载入在servlet生命周期内用到的一些数据,或者执行某些一次性的计算。
getLastModified方法为HttpServlet的方法,如果服务器接受到条件get请求(制定客户只想要那些自特定日期依赖发生的页面),系统会将指定的日期与getLastModified返回的日期进行比较,只返回那写在指定的日期之后发生发生改变的页面。
/**
* The standard service method compares this date against any date specified in the If-Modified-Since request header.
* If the getLastModified date is later or if there is no
* If-Modified-Since header, the doGet method is called
* normally. But if the getLastModified date is the same or earlier, the service method sends back a 304 (Not Modified)
* response and does not call doGet. The browser should use it cached version of the page in such a case.
* @param req
* @return
*/
由初始化参数控制的初始化:
要理解init参数的动机,您需要了解什么样的人可能希望对servlet的行为进行定制。它们主要可以分为3组:
1、开发人员
2、最终用户
3、部署人员
开发人员改变代码改变servlet行为
用户表单提交数据改变servlet行为
部署人员怎么办?init参数就是为了使管理员无需修改servlet的源代码就可以将servlet在机器间移动以及改变特定的参数。

1、web.xml中的servlet元素可以为servlet赋予名称
2、web.xml中的servlet为servlet指定一个定制URL
3、web.xml的servlet元素添加init-param子元素,可以指定初始化参数的名称和值
4、servlet的init方法中,调用getServletConfig获取servletConfig对象的引用
5、以init参数的名称为参数,调用ServletConfig的getInitParameter方法
destory方法:
服务器在移除servlet的实例前,会调用servlet的destory方法,可以使得servlet有机会关闭数据库连接等活动。但要注意web服务器有可能崩溃,因此不要将destory机制作为向磁盘上保存状态的唯一机制。
SignleThreadModel接口:
通常,系统只生成servlet的单一实例,之后为每个用户请求创建新的线程。所以doGet和doPost方法必须小心地同步对字段和其他共享数据访问。注意,多个线程并不共享局部变量,因此不需要特别的保护。
实现SignleThreadModel接口,系统会保证不会有多个请求线程同时访问该servlet的单个实例。使用了队列。但是服务器也可以创建由多个实例组成的池,同一时间每个实例都能够处理请求。然而,您依旧必须同步对类变量或存储在servlet之外的共享数据的访问。
SignleThreadModel尽管能阻止并发访问,但是实际上是不好的选择,原因:
1、同步对servlet的性能造成极大等待。应该考虑只同步操作共享数据那部分代码。
2、SingleThreadModel规范允许服务器使用多个实例来处理请求来替代对单个实例的请求进行排队的方案。只要每个实例同一时间只处理一个请求,实例共享的方式就满足规范的要求。但这不是一个好方案。
原因:
1、加入使用非静态存储共享数据。SingleThreadModel会阻止并发的访问,相当于把孩子和洗澡水同时泼掉了;每个servlet实例都拥有实例变量的单独副本,因此数据也不能正确共享。
2、加入使用静态共享数据。SingleThreadModel的共享池方式没有优点,多个请求仍然会并发访问静态数据。

例子:
String id = "User-ID-" + nextID;
out.println("<H2>" + id + "</H2>");
nextID = nextID + 1;
并发可能出问题。

解决方案:
1、减少竞争
修改为:String id = "User-ID-" + nextID++;
降低了并发问题的可能性,但是没有消除。和怒道情况下,降低错误出现的可能性是一件坏事而不是一件好事。
2、使用SingleThreadModel
前面说过坏处。
3、明确同步
synchronized(this) {
String id = "User-ID-" + nextID;
out.println("<H2>" + id + "</H2>");
nextID = nextID + 1;
}
servlet的调试:
1、println语句。
2、debug
3、日志
4、单元测试
5、检查源码
6、检查请求
7、检查响应
8、重启服务