由上一章节《servlet与jsp系列总结之一--servlet与jsp技术概述》可知servlet是什么,且它的功用是什么,这一章节我们来说说servlet基础。
一:servlet的基本结构:
下面的代码给出了一个基本的servlet,它处理get请求。get请求是浏览器请求的常用类型,用来请求web页面。在地址栏输入url,或点链接或提交没有指定method或指定了method="get"的表单时,浏览器都会生成这个请求。
import java.io.*; import java.servlet.*; import java.servlet.http.*; public class ServletTemplate extends HttpServlet{ public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{ //use "request" to read incoming HTTP headers(e.g.,cookies) and query data from HTML forms. //use "response" to specify the HTTP response status code and headers(e.g. the content type,cookies) PrintWriter out = response.getWriter(); //use "out" to send content to browser } }
认真看代码,特别是注释部分,应该说,以上代码表明了servlet的基本结构 。
servlet一般扩展HttpServlet ,并根据数据发送方式的不同(get或post) ,覆盖doGet或doPost方法 。如果要让servlet对get和post请求采用相同的行动,只需要让doGet调用doPost ,反之亦然。
doGet和doPost都接受两个参数 :HttpServletRequest和HttpServletResponse 。通过HttpServletRequest,可以得到所有的输入数据 ;同时,它提供了相应的方法,通过这些方法可以找出如表单(查询)数据、HTTP请求报头、和客户的主机名等信息 。通过HttpServletResponse可以指定输出信息 。如HTTP状态码(200,404等)和响应报头(Content-Type,Set-Cookie等) 。更重要 的是:通过HttpServletResponsesk可以获得PrintWriter ,通过PrintWriter可以将文档内容发送给客户 。对于简单的servlet,大部分的工作 就是用在println语句生成所期望的展示页面上。
由于doGet和doPost都抛出两种异常(ServletException和IOException),所以必须要方法声明中抛出它们。最后,注意引入的包:java.io(PrintWriter 等),java.servlet(HttpServlet 等),java.servlet.http(HttpServletRequest和HttpServletResponse )。
二:生成纯文本的servlet:
三: 生成HTML的servlet :
大多情况下我们生成的不是纯文本的servlet,而是生成HTML的servlet。要生成HTML的servlet,以下在三步:
1)告诉浏览器,即将向它发送HTML。
2)修改println语句,构建合法的web页面。
3)检查生成的html。
第一步是通过将http的Content-Type设置为text/html来完成。如下:
response.setContentType("text/html");
如上的html是servlet创建最常见的文档类型,但还有其它类型。如:使用servlet来生成excel表格 (application/vnd.ms-excel ),jpeg图像 (image/jped )和xml文档(text/xml) ,这三点情况也是很常见 的。
我们一般很少用servlet来构建内容相对固定的html,这种情况下用jsp就好了。
注意的一点是:必须在传送实际的文档之前设定内容的类型。
四:servlet的生命周期
服务器只创建每个servlet的单一实例 ,每个用户请求都会引发新的线程 ---将用户请求交付给相应的doGet和doPost方法来处理。
首先 创建servlet时,它的init方法 会得到调用,因此,init方法是放置一次性设置代码 的地方。之后 ,对于每个用户请求,都会创建一个线程,线程调用创建的servlet实例的相应方法。多个并发请求一般会导致多个线程同时调用service (尽管可以实现特殊的接口 singleThreadModel ---规定任何时间只允许单个线程运行)。之后,由service方法根据接收到的http请求的类型 ,调用doGet或doPost等方法。最后,如果服务器决定卸载某个servlet,它会首先调用servlet的destroy方法 。
service方法
服务器每次接收到对servlet的请求,都会产生一个新的线程,调用service方法。 service方法里首先检查http请求的类型再来根据相应的类型来调用doGet或doPost等方法。所以,我们99%的时间 都是在处理覆盖了的doGet或doPost方法。
注意的一点是:如果在servlet里要等同地处理get和post,让doGet调用doPost或相反 。而不要覆盖service方法。因为如果这样做,service方法里的其它请求如doPut等方法就乱了。
doGet,doPost和doXxx方法
service方法其它我们很少处理,doGet,doPost和doXxx方法是service方法的主体 ,我们关心的也是它们。
init方法
如果你希望servlet首次载入时,执行复杂的初始化工作,但不想每个请求都重复做这些工作时,就用init方法。
public void init() throws ServletException{ //Init code here... }
destory方法
在服务器移除servlet的实例之前,会调用servlet的destroy方法,在这里,我们可以关闭数据库连接、停止后台运行的线程、将cookie列表和点击计数写入磁盘、并执行其它清理工作。但是要注意服务器可能崩溃的情况,所以,可以将需要的数据如点击计数和cookie列表等值定期而主动地写入磁盘。
五:SingThreadModel接口
servlet只有一个实例,多个请求创建多个线程。如果新的请求到达,而前面的请求还在执行,那么多个线程可能会并发地访问同一个servlet对象。如果没有共享数据还好,但是有话,就要注意了 。doGet和doPost方法必须小心地对字段其它共享数据进行同步 ,因为多个线程可能会同时对这些数据进行访问。注意:多个线程并不共享局部变量,因此不需要保护。
在原则上,我们可以让servlet实现SingleThreadModel接口 ,来阻止多线程访问 ,如下:
public class YourServlet extends HttpServlet implements SingleThreadModel[ ................. }
这样,就保证不会有多个请求线程同时访问该servlet的单个实例。多个线程请求将排队,一次只将一个请求转给单个servlet实例。
尽管SingleThreadModel能阻止并发的访问,但通常不是好选择,除了对性能(等待时间)造成影响外,还对共享数据产生不安全。所以,不要让高流量的servlet实现SingleThreadModel,明确代码同步要好一些。 在servlet规范的2.4版本就不造成使用SingleThreadModel。
以下有个例子:
import java.io.*; import java.servlet.*; import java.servlet.http.*; public class UserIDs extends HttpServlet{ private int newxtID = 0; public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{ response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html><doby>"); String id = "USER-ID" + nextID; out.println("<span>"+id + "</span>"); nextID = nextID+1; out.println("</doby></html>"); } }
注意以上代码构造html内容的写法。
在上面的代码中,如果多几次用户请求就会出现重复的问题。因为可能第一个线程的nextID还没有加1,第二个线程就执行过来了,所以出现重复的nextID,这是很严重的问题,特别是在银行等领域中。
怎么来解决呢?考虑一下有,有以下三种:
1)改少竞争:
把
nextID = nextID+1;
改成:
nextID++;
使得单个线程较快执行下来,但只能减少错误发生的可能性,这不能解决问题,。
2)用SingleThreadModel:
public class UserIDs extends HttpServlet implements SingleThreadModel{ }
前面说过,这种方式将产生性能下下降m时,如果服务器通过产生servlet的多个实例来实现SingleThreadModel,这种方式会完全失败,因为每个实例都会拥有自己的nextID字段。
3)明确地同步对代码的访问。
用java的标准同步构造,在首次访问共享数据之前开启一个synchronized块,守成数据的更新之后关闭这个块即可。如下:
synchronized{ String id = "USER-ID" + nextID; out.println("<span>"+id + "</span>"); nextID = nextID+1; }
一旦一个线程进入代码块,只有等待它退出为止,其它线程不允许进来。java中常用这种解决方案 。忘记易于出错且性能低下的SingleThreadModel吧!