Java Servlet 技术
JavaServlet技术
StephanieBodoff
当Web刚末尾被用来传送服务时,服务提供者就已经看法到了动态内容的需求。Applet是为了完成这个指标的一种最早的尝试,它次要关注利用客户端平台来交付动态用户体验。与此同时,开发人员也在钻研如何利用服务器平台完成这个指标。末尾的时分,公共网关接口(CommonGatewayInterface,CGI)脚本是生成动态内容的次要技术。只管利用得十分宽泛,但CGI脚本技术有很多的毛病,这包括平台相干性和缺乏可扩充性。为了避免这些局限性,JavaServlet技术因应而生,它可以以一种可移植的方法来提供动态的、面向用户的内容。
什么是Servlet?
一个servlet就是Java编程言语中的一个类,它被用来扩充服务器的功能,服务器上驻留着可能经过“申请-呼应”编程模型来访问的运用程序。只管servlet可能对任何类型的申请产生呼应,但通常只用来扩充Web服务器的运用程序。JavaServlet技术为这些运用程序定义了一个特定于HTTP的servlet类。
javax.servlet和javax.servlet.http包为编写servlet提供了接口和类。一切的servlet都必须完成Servlet接口,该接口定义了生命周期方法。
当完成一个通用的服务时,您可能利用或扩充由JavaServletAPI提供的GenericServlet类。HttpServlet类提供了一些方法,诸如doGet和doPost,以用于解决特定于HTTP的服务。
本章次要讲述如何编写对HTTP申请产生呼应的servlet。这里假定您已经了解了一些HTTP协定的根底知识。假设对这些协定不相熟的话,您可能从HTTP概述中对HTTP协定有一个初步的了解。
Servlet示例
本章利用Duke'sBookstore运用程序来阐明与servlet编程相干的义务。表14-1列出了处理每个书店性能的servlet。每个编程义务用一个或多个servlet来阐明。例如,BookDetailsServlet阐明如何解决HTTPGET申请,BookDetailsServlet和CatalogServlet显示如何构建呼应,而CatalogServlet则阐明如何跟踪会话信息。
表14-1Duke'sBookstoreServlet例子
性能
Servlet
进入书店
BookStoreServlet
创建书店标识
BannerServlet
阅读书店的目录
CatalogServlet
将书放入购物车
CatalogServlet,
BookDetailsServlet
获取关于特定的某本书的一些具体信息
BookDetailsServlet
显示购物车
ShowCartServlet
从购物车中移除一本或多本书
ShowCartServlet
购买购物车中的书
CashierServlet
获得对购买确实认
ReceiptServlet
这些书店运用程序的数据保存在数据库中,并经过协助类database.BookDB停止存储。database包也包括BookDetails类,一个BookDetails类用来代表一种书。购物车和购物车项用cart.ShoppingCart和cart.ShoppingCartItem来分别示意。
书店运用程序的源代码放在<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1目录中,这个目录是在对指南包停止解紧缩时创建的。
要构建、装置和运转这个实例,需实现以下步骤:
1.在终端窗口中,转到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1。
2.运转build。build指标将产生任何必要的编码并且将文件间接拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore1/build。
3.确认Tomcat已经末尾执行。
4.运转antinstall。install指标告诉Tomcat已经有了新的上下文。
5.启动PointBase公司的数据库服务器,并且在没齐全预备好的情况下仍然指向数据库(见从Web运用中访问数据库)。.
6.打开书店的URL:8080/bookstore1/enter以运转该运用程序。
要部署该运用程序,要实现以下步骤:
1.运转antpackage。这个包义务是创建一个WAR文件,该文件蕴含WEB-INF/classes中的运用程序类和META-INF中的context.xml文件。
2.确认Tomcat已经末尾执行。
3.运转antdeploy。Deploy指标将WAR拷贝到Tomcat,并且告诉已经有了新的上下文。
缺点扫除
普通的成绩和其处理计划罗列了Web客户端为什么会失败的一些缘由。另外,Duke书店前往了以下同样:
·BookNotFoundException——假设一本书不能在书店的数据库中找到,则前往该同样。假设用户没有运转antcreate-book-db来加载书店数据库中的数据、或没有运转数据库服务器、或数据库已经解体,这些都将产生该同样。
·BooksNotFoundException——假设书店的数据不能被获取,则前往该同样。假设用户没有运转antcreate-book-db来加载书店数据库中的数据、或没有运转数据库服务器、或数据库已经解体,这些都将产生该同样。
·UnavailableException——假设servlet不能获取到用来示意书店的Web上下文属性,则前往该同样。假设您没有拷贝指向(PointBase)的客户端库<PB_HOME>/lib/pbclient45.jarto<JWSDP_HOME>/common/lib、或许假设指向的(PiontBase)服务器没有运转、或用户没有定义Tomcat中用来引用指向数据库(PointBase)的数据源,这都将产生该同样。
由于指定了一个谬误页,用户将看到这样的一个消息Theapplicationisunavailable.Pleasetrylater.假设指定了一个正确页,Web容器将产生一个蕴含AServletExceptionHasOccurred消息的默许页和一个用来协助诊断同样产生缘由的栈。假设利用errorpage.html,用户可能了解Web容器决议同样产生缘由的日志。Web日志位于<JWSDP_HOME>/logs目录中,由jwsdp_log.<date>.txt来命名。
Servlet的生命周期
一个servlet的生命周期由部署servlet的容器来控制。当一个申请映射到一个servlet时,该容器执行下列步骤。
1.假设一个servlet的实例并不存在,Web容器
a.加载servlet类。
b.创建一个servlet类的实例。
c.调用init初始化servlet实例。该初始化过程将在初始化servlet中讲述。
2.调用service方法,传递一个申请和呼应答象。服务方法将在编写服务方法中讲述。
假设该容器要移除这个servlet,可调用servlet的destroy方法来完结该servlet。完结过程将在完结Serlvet中探讨。
解决Servlet生命周期事情
在servlet的生命周期中,用户可能经过定义监听器对象对事情停止检测和产生反应。当生命周期事情发生时,调用该对象的方法。要利用这些监听器对象,用户必须定义监听器类,并且指定相应的监听器类。
定义监听器类
您可能将监听器类定义为一个listener接口的完成。Servlet生命周期事情列出了可能检测的事情和相应的必须完成的接口。当调用一个监听器方法时,需向该方法传递一个蕴含事情适当信息的事情。例如,向HttpSessionListener接口中的方法传递的是一个HttpSessionEvent事情,这个事情蕴含了一个HttpSession。
表14-2Servle生命周期事情
对象
事情
监听器接口和事情类
Web上下文
(见访问Web上下文)
初始化和销毁
javax.servlet.
ServletContextListener和
ServletContextEvent
属性的减少、删除或替代
javax.servlet.
ServletContextAttributeListener和
ServletContextAttributeEvent
会话
(见维护客户给形状)
创建、失效和超时
javax.servlet.http.
HttpSessionListener和
HttpSessionEvent
属性的减少、删除或替代
javax.servlet.http.
HttpSessionAttributeListener和
HttpSessionBindingEvent
listeners.ContextListener类担任创建和移除在Duke书店运用程序中利用的数据库助手和计数器对象。方法从ServletContextEvent中获取Web上下文对象,进而存储(和移除)作为servlet上下文属性的对象。
importdatabase.BookDB;
importjavax.servlet.*;
importutil.Counter;
publicfinalclassContextListener
implementsServletContextListener{
privateServletContextcontext=null;
publicvoidcontextInitialized(ServletContextEventevent){
context=event.getServletContext();
try{
BookDBbookDB=newBookDB();
context.setAttribute("bookDB",bookDB);
}catch(Exceptionex){
System.out.println(
"Couldn'tcreatedatabase:"
+ex.getMessage());
}
Countercounter=newCounter();
context.setAttribute("hitCounter",counter);
context.log("CreatedhitCounter"
+counter.getCounter());
counter=newCounter();
context.setAttribute("orderCounter",counter);
context.log("CreatedorderCounter"
+counter.getCounter());
}
publicvoidcontextDestroyed(ServletContextEventevent){
context=event.getServletContext();
BookDBbookDB=context.getAttribute(
"bookDB");
bookDB.remove();
context.removeAttribute("bookDB");
context.removeAttribute("hitCounter");
context.removeAttribute("orderCounter");
}
}
指定事情监听器类
为了指定一个事情监听器类,用户要为Web运用部署形容符减少一个listener元素。以下就是Duke书店运用程序的一个listener元素。
<listener>
<listener-class>listeners.ContextListener</listener-class>
</listener>
解决谬误
当servlet执行时,能够产生许多同样。而当同样产生时,Web容器将产生一个蕴含AServletExceptionHasOccurred消息的缺省页。然而,用户也可前往一个容器,该容器应蕴含为给定同样指定的谬误页。为了指定这样一个页,用户要为Web运用减少部署形容符减少一个error-page元素。这些元素将Duke书店运用程序前往的同样映射到errorpage.html:
<error-page>
<exception-type>
exception.BookNotFoundException
</exception-type>
<location>/errorpage.html</location>
</error-page>
<error-page>
<exception-type>
exception.BooksNotFoundException
</exception-type>
<location>/errorpage.html</location>
</error-page>
<error-page>
<exception-type>exception.OrderException</exception-type>
<location>/errorpage.html</location></error-page>
共享信息
像大少数对象一样,Web组件通常与其余一些对象协同工作,以实现义务。要做到这一点,可能有多种方法。Web组件可能利用公有的helper(助手)对象(例如,JavaBeans组件),也可能共享那些有公共作用域属性的对象,它们可能利用数据库,还可能调用其余的Web资源。JavaServlet技术机制容许一个Web组件调用其余的Web资源,这在调用其余Web资源中有形容。
利用作用域对象
几个协作的Web组件经过一些对象来共享信息,这些对象是作为四个作用域对象的属性来维护的。这些属性可能经过示意域的类的[get|set]Attribute方法访问。表14-3列出了这个作用域对象。
表14-3作用域对象
作用域对象
类
哪些组件可能对其停止访问
Web上下文
javax.servlet.
ServletContext
Web上下文中的Web组件。见访问Web上下文
会话
javax.servlet.
http.HttpSession
解决属于会话的申请的Web组件。见维护客户端形状。
申请
javax.servlet.
ServletRequest
的子类型
解决申请的Web组件。
页
javax.servlet.
jsp.PageContext
创建对象的JSP页。见隐式对象。
图14-1显示了Duke书店运用程序维护的作用域属性。
图14-1Duke书店作用域属性
控制对共享资源的并发访问
在多线程的服务器中,能够出现对共享资源的并发访问。除了作用域对象属性外,共享资源还包括存储器中的数据(照实例和类变量)、外部对象(如文件)、数据库衔接和网络衔接。并发访问可出如今多个情况下。
·多个Web组件访问存储在Web上下文中的对象。t
·多个Web组件访问存储在会话中的对象。
·一个Web组件中的多个线程访问实例变量。一个Web容器普通为每个申请创建一个线程来解决。假设用户确认一个servlet实例每次只解决一个申请,servlet就能完成SingleThreadModel接口。假设servlet完成了这个接口,用户就能确保servlet的服务方法中不能够有两个线程并发执行。Web容器可经过同步访问一个servlet的单独实例、或许经过维护一个Web组件池为每个实例调用一个新的申请来完成。这个接口并不能防止Web组件访问共享资源(如静态类变量、外部对象)导致的同步成绩
当资源可能并发访问时,利用资源也就可能用不分歧的模式。为了防止这样的情况发生,用户必须利用在Java指点中的线程单元中形容的同步机制来控制访问。
在以前的局部中,咱们阐明了被多个servlet共享的5个作用域属性:bookDB,cart,currency,hitCounter和orderCounter。bookDB属性将在下一节中探讨。cart,currency和counter可能被多线程的servlet设置和读。利用同步方法来控制访问以防止这些对象的利用不分歧。例如,下面是一个util.Counter类:
publicclassCounter{
privateintcounter;
publicCounter(){
counter=0;
}
publicsynchronizedintgetCounter(){
returncounter;}
publicsynchronizedintsetCounter(intc){
counter=c;returncounter;
}
publicsynchronizedintincCounter(){
return(++counter);
}
}
访问数据库
在Web组件之间共享,并且在对一个Web运用被调用的间隙内维持的数据通常是由一个数据库来维护的。Web组件利用JDBC2.0API来访问关系数据库。书店运用程序的数据由数据库来维护,并经过助手类database.BookDB访问。例如,当用户购买书后,ReceiptServlet调用BookDB.buyBooks方法来更旧书的清单。buyBooks方法为每本蕴含在购物车中的书调用buyBook。为了确保命令被齐全执行,buyBook的调用程序将被包装在一个单独的JDBC事务解决中。经过[get|release]Connection方法可能使共享数据库衔接同步利用。
publicvoidbuyBooks(ShoppingCartcart)throwsOrderException{
Collectionitems=cart.getItems();
Iteratori=items.iterator();
try{
getConnection();
con.setAutoCommit(false);
while(i.hasNext()){
ShoppingCartItemsci=(ShoppingCartItem)i.next();
BookDetailsbd=(BookDetails)sci.getItem();
Stringid=bd.getBookId();
intquantity=sci.getQuantity();
buyBook(id,quantity);
}
con.commit();
con.setAutoCommit(true);
releaseConnection();
}catch(Exceptionex){
try{
con.rollback();
releaseConnection();
thrownewOrderException("Transactionfailed:"+
ex.getMessage());
}catch(SQLExceptionsqx){
releaseConnection();
thrownewOrderException("Rollbackfailed:"+
sqx.getMessage());
}
}
}
初始化Servlet
在Web容器加载和实例化servlet类之后、servlet实例传递来自客户端的申请之前,Web容器对servlet停止初始化。用户可能自定义这个初始化过程,以容许servlet读持久的配置数据、初始化资源,并且疏忽Servlet接口的init方法以执行任何其它的一次性的流动。servlet必须利用UnavailableException来实现初始化过程。
一切的访问书店数据库的servlet(BookStoreServlet,CatalogServlet,BookDetailsServlet,和ShowCartServlet)在它们的init方法中初始化一个变量,指向用Web上下文监听器创建的数据库助手对象。
publicclassCatalogServletextendsHttpServlet{
privateBookDBbookDB;
publicvoidinit()throwsServletException{
bookDB=(BookDB)getServletContext().
getAttribute("bookDB");
if(bookDB==null)thrownew
UnavailableException("Couldn'tgetdatabase.");
}
}
编写服务方法
servlet提供的服务虚如今GenericServlet的service方法、HttpServlet的doMethod方法(在该方法中,Method可能带Get、Delete、Options、Post、Put、Trace的值),或许是任何其余的由完成了Servlet接口的类定义的协定指定(protocol-specific)的方法中。在这一章剩下的局部中,服务方法这个术语将用于在一个向客户端提供服务的servlet类中定义的任何方法。
服务方法的普通形式是从申请中提取信息、访问外部资源并且基于这些信息填充呼应。
对于HTTPservlet来说,填充呼应的正确过程是:首先填充呼应头,然后从呼应中获取一个输出流,最后编写输出流的一切主体内容。呼应头必须在PrintWriter或ServletOutputStream被获取到之前设置好,由于HTTP协定宿愿获得主体内容前的一切头的信息。下两节将形容如何从申请中获得信息和产生呼应。
从申请中获得信息
一个申请蕴含客户端和servlet之间传递的数据。一切申请都完成了ServletRequest接口,该接口为访问一下的信息定义了方法:
·参数,通常用来在客户端和servlet之间传送信息
·对象属性(Object-valuedattribute),通常用来在servlet容器与servlet之间或在协作的servlet之间传递信息
·无关协定的信息,用来在申请、客户端和触及到该申请中的服务器之间的通讯。
·无关地区化的信息。
例如,在CatalogServlet中,顾客宿愿购买的书的标识符作为参数蕴含在申请中。下面的这段代码阐明了如何利用getParameter方法提取标识符。
StringbookId=request.getParameter("Add");
if(bookId!=null){
BookDetailsbook=bookDB.getBookDetails(bookId);
用户也可能从申请中获取一个输入流,并对数据停止手工解析。要读字符数据,可能利用由申请的getReader方法前往的BufferedReader对象来实现。而要读二进制数据,可能利用getInputStream前往的ServletInputStream。
HTTPserlvet经过HTTP申请对象传递,HttpServletRequest蕴含了请URL、HTTP头、查询字符串等等。
一个HTTP申请URL蕴含以下几局部:
[host]:[port][requestpath]?[querystring]
申请门路由以下元素组成:
·上下文门路:向前的斜线/和servlet的Web运用的上下文根的拼接。
·servlet门路:与激活该申请的组件别名相应的门路局部,由向前的斜线/末尾。
·门路信息:申请门路的局部,不是上下文门路或许servlet门路的局部。
假设上下文门路是/catalog和表14-4罗列出的别名,表14-5给出了一些实例,阐明如何分解URL。
表14-4别名
形式
Servlet
/lawn/*
LawnServlet
/*.jsp
JSPServlet
表14-5申请门路元素
申请门路
Servlet门路
门路信息
/catalog/lawn/index.html
/lawn
/index.html
/catalog/help/feedback.jsp
/help/feedback.jsp
null
查询字符串由参数和值的汇合组成。每个参数都是从申请中用getParameter方法获取失去的。这里有两种方法产生查询字符串:
·一个查询字符串能在Web页中明白地显示进去。例如,一个HTML页由CatalogServlet产生,该HTML页蕴含了<ahref="/bookstore1/catalog?Add=101">AddToCart</a>。CatalogServlet将命名为Add的参数提出,如下:
StringbookId=request.getParameter("Add");
·当一个表单与一个GETHTTP方法一同被提交时,在URL上附加一个查询字符串。在Duke书店运用程序中,首先CashierServlet产生了一个表单,然后在表单中输入一个用户名,该表单附加在映射到ReceiptServlet的URL上,最后ReceiptServlet利用getParameter方法提取用户名。