Servlet容器是如何工作的?
2007-11-17 15:36
453 查看
HowServletContainersWork
byPrimitiveServlet
(位于webroot/目录下)测试这个容器.更复杂的servlet已经超出了这个容器的能力,但你可以从HowTomcatWorks这本书中学习到怎样建立更完善的servletcontainer.这些程序的类都是
ex02.pyrmont包中的一部分.要想了解程序是如何工作的,你必须要熟悉
javax.servlet.Servlet接口.为了重新温习一下这方面的知识,本文第一部分将讨论这个接口.然后,你会学习到究竟一个servletcontainer需要做些什么工作才能处理servlet.
Thejavax.servlet.Servlet
Interface
Servlet编程需要用到2个包中的类和接口:javax.servlet和
javax.servlet.http.在这些类和接口中,
javax.servlet.Servlet接口是最重要的接口.所有servlet都必须实现这个接口或者继承一个实现了这个接口的类.
Servlet接口有5个方法,定义如下:
init,
service,和
destroy方法是servlet的生命周期方法.
init方法只在servlet类被初始化后被容器调用一次,指出这个servlet已经处于可提供服务的状态.在
init方法的调用必须完全成功后,servlet才能够接收任何请求.Servlet程序员可以覆盖这个方法,实现一些需要只运行一次的初始化代码,像装载databasedriver,初始化值,等.其它情况下,这个方法通常是空白的.
service方法会被容器调用让这个servlet响应一个请求.servletcontainer传递一个
javax.servlet.ServletRequest对象和一个
javax.servlet.ServletResponse对象.
ServletRequest对象包含客户端HTTP请求信息,
ServletResponse封装了servlet的响应.这2个对象让你能够写一些自己的代码决定这个servlet怎样为这个客户端请求提供服务.servletcontainer在移除一个servlet实例之前会调用
destroy方法.这通常发生在当servletcontainer要关闭或者当servletcontainer需要更多freememory的时候.这个方法只会在这个servlet的
service
方法中的所有线程都已经退出来或者超时时间已过之后.在servletcontainer调用了
destroy
之后,它就不会再调用这个servlet上的
service方法.
destroy方法给了servlet一个机会去清理该servlet正在把持的任何资源(例如,内存,文件句柄,和线程)和确认任何持久性状态信息都已经与内存中这个servlet的当前状态进行了同步.Listing2.1
包含了
PrimitiveServlet
的代码,这是一个非常简单的servlet,你可以用来测试本文这个servletcontainer程序.
PrimitiveServlet类实现了
javax.servlet.Servlet(所有servlet都必须实现)并且为所有5个servlet方法都提供了实现.它做的事情非常简单:每次
init,
service,或者
destroy方法中的任意一个被调用,servlet就向控制台打印方法名.
service方法中的代码也会从
ServletResponse
对象获得一个
java.io.PrintWriter对象向浏览器发送字符串.Listing2.1.
PrimitiveServlet.java
Application1
现在,让我们从servletcontainer的视角来看servlet编程.简而言之,一个功能完备的servletcontainer需为每个servlet的HTTP请求完成如下工作:当servlet当servlet第一次被调用的时候,加载这个servlet类并调用它的init方法(只调用一次).对于每个请求,构造一个
javax.servlet.ServletRequest实例和
javax.servlet.ServletResponse
实例.调用servlet的
service方法,传给它
ServletRequest和
ServletResponse对象.当servlet类被关掉的时候,调用servlet的
destroy方法并卸载servlet类.在一个真正的servletcontainer里需要做的比这些复杂的多.不过,这个简单的servletcontainer功能并不完备.因此,它只能运行非常简单的servlet,而且也不调用servlet的
init和
destroy方法.取而代之,它做了如下工作:等待HTTP请求.构造一个
ServletRequest对象和一个
ServletResponse对象.如果请求的是静态资源,调用
StaticResourceProcessor
实例的
process方法,并传给它
ServletRequest和
ServletResponse对象.如果请求的是一个servlet,加载这个servlet类,调用它的
service方法,并传递
ServletRequest和
ServletResponse对象给它.注意,在这个servletcontainer中,请求的servlet类会每次都加载.在第一个程序里,servletcontainer由6个类组成:
HttpServer1
Request
Response
StaticResourceProcessor
ServletProcessor1
Constants就像上一篇文章中的程序那样,这个程序的入口点(thestatic
mainmethod)在
HttpServer类中.这个方法创建了
HttpServer的一个实例并调用它的
await方法.就像名字所隐含的,这个方法会等待HTTP请求,创建一个
Request对象和一个
Response对象,然后分发给一个
StaticResourceProcessor实例或者一个
ServletProcessor实例,这依赖于请求的是静态资源还是一个servlet.
Constants类包含staticfinal
WEB_ROOT这个被其它类引用的变量.
WEB_ROOT指示了
PrimitiveServlet的位置和这个容器所能提供的静态资源.
HttpServer1实例一直等待HTTP请求知道它接收到关闭命令.发出关闭命令与上一篇文章里所做的一样.这个程序里的每个类都会在下面的章节里讨论到.
TheHttpServer1
Class
这个程序的HttpServer1类与上一篇文章中的那个简单的webserver程序的
HttpServer
类很相似.不过,在这个程序里,
HttpServer1能够同时支持静态资源和servlet.如果要请求静态资源,使用下面形式的URL:这正是上一篇文章中,你如何请求webserver程序的一个静态资源.要想请求一个servlet,你需使用下面的URL:因此,如果你想使用浏览器请求一个叫
PrimitiveServlet
的
servlet,在浏览器地址栏中输入如下URL:Listing2.2中给出的类的
await方法,等待HTTP请求,直到发出关闭命令为止.这跟上一篇文章讨论到的
await方法很类似.Listing2.2.The
HttpServer1class'
awaitmethodListing2.2中的
await
方法与前一篇文章的
await
方法的区别是,Listing2.2中,request可以分发至
StaticResourceProcessor或者
ServletProcessor.如果URI包含字符串"
/servlet/",request会被forward到后者.否则,request会被传给
StaticResourceProcessor实例.
TheRequest
Class
Servlet的service方法从servletcontainer接收一个
javax.servlet.ServletRequest实例和一个
javax.servlet.ServletResponse实例.container因而必须要构造一个
ServletRequest对象和一个
ServletResponse对象并传给这个servlet的
service方法.
ex02.pyrmont.Request类代表要传递给
service方法的request对象.同样的,它必须实现
javax.servlet.ServletRequest接口.这个类必须提供接口中所声明的所有方法的实现.但是,我们想使它简单一些,所以只实现了部分方法.要想成功编译这个
Request类,你需要给其它一些方法空的实现.如果你看了
Request类,你会看到所有方法声明中返回对象实例的方法都返回的是一个
null,如下:另外,
Request类也有
parse和
getUri方法,这些上篇文章讨论过.
TheResponse
Class
Response类实现了
javax.servlet.ServletResponse.同样地,该类必须提供接口中的所有方法的实现.类似于
Request类,我把所有方法都实现为空除了
getWriter方法.传给
PrintWriter类构造器的第二个参数是一个Boolean,指示是否要启用
autoflush.传递true作为第二个参数将使每次对
println方法的调用都flush输出.但是,
service方法中的最后一行代码调用
Response类也有一个我们在上一篇文章里讨论到的
sendStaticResource方法.
TheStaticResourceProcessor
Class
StaticResourceProcessor类用来处理请求静态资源的请求.它唯一的方法是
process,如Listing2.3所示.Listing2.3.The
StaticResourceProcessorclass'
processmethod
process方法接收2个参数:一个
Request实例和一个
Response实例.它只简单的调用了一下
Response类的
sendStaticResource
方法.
TheServletProcessor1
Class
ServletProcessor1类处理HTTP请求.它令人惊讶的简单,只包含
process方法.这个方法接收2个参数:一个
javax.servlet.ServletRequest实例和一个
javax.servlet.ServletResponse
实例.
process方法也构造了一个
java.net.URLClassLoader对象并用它来加载servlet类文件.从classloader得到了
Class对象后,
process方法创建了这个servlet的一个实例并调用
service方法.Listing2.4给出了
process方法.Listing2.4.The
ServletProcessor1class'
processmethod
process方法接收2个参数:一个
ServletRequest实例和一个
ServletResponse
实例.它从
ServletRequest
的
getRequestUri
方法获得URI:记住,URI是如下格式的:servletName是servlet类的名字.要想加载servlet类,我们必须要从URI中知道servlet的名字,我们是通过
process方法中的下面这行代码获得的:接着,
process方法加载这个servlet.为了加载这个servlet,你必须要创建一个classloader并告诉这个classloader这个类的位置.这个servletcontainer指示classloader在
Constants.WEB_ROOT
指向的路径下查找.
Constants.WEB_ROOT指向工作路径下的webroot/目录.要加载一个servlet,请使用
java.net.URLClassLoader类,这是
java.lang.ClassLoader
的间接子类.一旦你有了
URLClassLoader类的实例,就可以使用它的
loadClass方法加载一个servlet类.初始化
URLClassLoader类非常简单.这个类有3种构造器,最简单的如下:
urls是
java.net.URL对象的数组,
java.net.URL对象指向当加载一个类时所要搜寻的位置.任何以
/结尾的URL都假定是一个目录.否则,URL假定应用的是一个.jar文件,必要的时候,会下载并打开这个.jar文件.在servletcontainer里,一个classloader能够查找到servlet类的地方叫做repository.在我们的程序里,只有一处classloader必须查看—位于工作路径下的webroot/目录.因此,我们先创建了一个包含单个URL的数组.
URL类提供了好几种构造器,所以有很多方式去构造一个URL对象.对于本程序,我使用了与Tomcat中的另一个类所使用的相同的构造器.该构造器有如下名称:你可以这样使用这个构造器:传递一个specification作为第二个参数,
null
作为它的第一个和第三个参数.但是,还有另一种构造器,它也接收3个参数:因此,如果你像下面这样写,编译器将不知道你要用的是哪个构造器:你可以这样解决:告诉编译器第三个参数的类型:对于第二个参数,传递一个包含repository的
String(thedirectorywhereservletclassescanbefound).使用下面的代码创建:把所有部分综合起来,下面就是
process方法如何构造了正确的
URLClassLoader实例:生成repository的代码来自
org.apache.catalina.startup.ClassLoaderFactory
类的
createClassLoader方法,生成URL的代码取自
org.apache.catalina.loader.StandardClassLoader类的
addRepository方法.但,在这里你不必担心这些类.有了classloader,你就能使用
loadClass方法装载servlet类:接着,
process方法为装载进来的servlet类创建一个实例,向下造型为
javax.servlet.Servlet,并调用servlet的
service方法:
CompilingandRunningtheApplication
要编译程序,在工作路径敲入如下命令:如果在Windows上运行程序,需在工作路径下敲入如下命令:在Violetsareblue)因为只有第一个字符串才flush到浏览器.HowTomcatWorks这本书的后面章节所附带的程序会给你演示如何解决这个问题.
Application2
在第一个程序里有一个严重的问题.在ServletProcessor1类的
process方法,我们向上类型转换了
ex02.pyrmont.Requestto
javax.servlet.ServletRequest
的实例,把它作为第一个参数传递给了servlet的
service方法.我们也向上造型了
ex02.pyrmont.Responseto
javax.servlet.ServletResponse的实例并把它作为第二个参数传递给了servlet的
service方法.这危及
ServletRequest和
ServletResponse实例到
Request和
Response并调用它们的public方法.有了
Request实例,他们就可以调用它的
parse方法.有了
Response实例,他们就能够调用它的
sendStaticResource方法.你不能把
parse和
sendStaticResource方法设置为private,因为在
ex02.pyrmont包里的其它类会调用.但是,我们不打算让这两个方法在一个servlet内部可以使用.一种解决方案是给
Request和
Response类设置一个默认的访问修饰符,这样它们就不能在
ex02.pyrmont包的外部使用.但是,有一个更优雅的解决方案:使用facade类.在第二个程序里,我们加了2个facade类:
RequestFacade和
ResponseFacade.
RequestFacade类实现了
ServletRequest接口,传递一个
Request实例进行初始化,这个Request实例被指派给了它的构造器中的ServletRequest对象引用.
ServletRequest接口中的每个方法实现都调用
Request对象的相应方法,但
ServletRequest对象自己则是private并且在类外不能被访问到.与将
Request对象向上造型为
ServletRequest并传递给
service方法相反,我们构造了一个
RequestFacade对象并传递给
service方法.servlet
ServletRequest实例向下造型回
RequestFacade;可是,他不能只访问
ServletRequest接口中的可用方法.现在,
parseUri方法已经安全了.Listing2.5展示了不完整的
RequestFacade类.Listing2.5.The
RequestFacadeclass注意
RequestFacade
的构造器.它接收一个
Request对象但马上指派给了私有的
servletRequest对象引用.也要注意
RequestFacade类里的每个方法都调用了
ServletRequest对象中的相应方法.在
ResponseFacade类中也是如此.这些是在Application2中使用到的类:
HttpServer2
Request
Response
StaticResourceProcessor
ServletProcessor2
Constants
HttpServer2类类似于
HttpServer1,除了在
await方法中它使用的是
ServletProcessor2,而不是
ServletProcessor1:
ServletProcessor2类类似于
ServletProcessor1,除了
process方法中的如下这部分代码:
CompilingandRunningtheApplication
在工作目录敲入如下命令编译程序.如果在Windows运行这个程序,typethefollowingcommandfromtheworkingdirectory:在Summary
本文讨论了一个简单的Servlet容器,可以用来提供静态资源服务,也能处理像PrimitiveServlet
这样简单的
servlet.它也提供了
javax.servlet.Servlet接口的背景信息.
相关文章推荐
- Servlet容器是如何工作的
- Spring+Servlet整合(如何向Servlet注入属性(转),servlet获取spring容器中的bean)
- 如何让servelt和Filter注入到spring容器或者在servlet中调用spring中的bean
- servlet容器如何处理http请求?
- SpringMVC容器中Servlet如何调用service层接口
- Servlet容器工作原理讲解(一)
- Servlet容器工作原理讲解(1)
- 如何编写dao_servlet容器处理请求资源路径_路径匹配
- Servlet容器工作原理讲解(1)
- 如何设计Servlet容器启动时加载多个数据到内存
- Servlet容器工作原理讲解(2)
- 如何加深对Servlet及其工作模式的理解
- 在部署到Servlet容器之前,如何生成包含SpringBoot的War文件
- tomcat6容器中的Servlet的工作模式解析
- Servlet容器如何同时来处理多个请求
- Servlet容器工作原理讲解(3)
- mysql处理中文要注意的、servlet生命周期、容器如何处理请求资源路径
- jsp变成servlet之容器如何处理jsp
- Servlet容器如何同时来处理多个请求
- SpringMVC是如何让Controler替代Servlet工作的