您的位置:首页 > 其它

Servlet容器是如何工作的?

2007-11-17 15:36 453 查看
 

HowServletContainersWork

byBudiKurniawan05/14/2003编辑注:[b]在这个系列中,[b]本文和上一篇,"HowWebServersWork,"[b]是Tomcat[b]内部工作原理的指南书籍HowTomcatWorks的节选.如果你还没有读过上一篇,[b]那么赶紧先去读读吧;[b]那篇文章告诉了你一些有用的背景信息.[b]在本文里,[b]你会学习到怎样去创建2[b]个servlet[b]容器.[b]本书附带的程序可以下载.[b]如果你有兴趣的话,[b]在限定的时间内,[b]作者的网站上允许下载本书的其它部分.[/b][/b][/b][/b][/b][/b][/b][/b][/b][/b][/b][/b][/b][/b]本文讲解了一个简单的servlet容器是怎样工作的.将会给您展示2个servlet容器应用程序;第一个尽可能简单,第二个则在第一个基础上做了美化.我不想把第一个容器做的完美的唯一原因是让它尽可能保持简单.更多复杂的servlet容器,包括Tomcat4和5,则在HowTomcatWorks的其它章节讨论.servletcontainer既能处理简单的servlet,也能处理静态资源.你可以使用
PrimitiveServlet
(位于
webroot/目录下)测试这个容器.更复杂的servlet已经超出了这个容器的能力,但你可以从HowTomcatWorks这本书中学习到怎样建立更完善的servletcontainer.这些程序的类都是
ex02.pyrmont
包中的一部分.要想了解程序是如何工作的,你必须要熟悉
javax.servlet.Servlet
接口.为了重新温习一下这方面的知识,本文第一部分将讨论这个接口.然后,你会学习到究竟一个servletcontainer需要做些什么工作才能处理servlet.

The
javax.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
main
method)在
HttpServer
类中.这个方法创建了
HttpServer
的一个实例并调用它的
await
方法.就像名字所隐含的,这个方法会等待HTTP请求,创建一个
Request
对象和一个
Response
对象,然后分发给一个
StaticResourceProcessor
实例或者一个
ServletProcessor
实例,这依赖于请求的是静态资源还是一个servlet.
Constants
类包含staticfinal
WEB_ROOT
这个被其它类引用的变量.
WEB_ROOT
指示了
PrimitiveServlet
的位置和这个容器所能提供的静态资源.
HttpServer1
实例一直等待HTTP请求知道它接收到关闭命令.发出关闭命令与上一篇文章里所做的一样.这个程序里的每个类都会在下面的章节里讨论到.

The
HttpServer1
Class

这个程序的
HttpServer1
类与上一篇文章中的那个简单的webserver程序的
HttpServer
类很相似
.不过,在这个程序里,
HttpServer1
能够同时支持静态资源和servlet.如果要请求静态资源,使用下面形式的URL:
这正是上一篇文章中,你如何请求webserver程序的一个静态资源.要想请求一个servlet,你需使用下面的URL:
因此,如果你想使用浏览器请求一个叫
PrimitiveServlet
servlet
,在浏览器地址栏中输入如下URL:
Listing2.2中给出的类的
await
方法,等待HTTP请求,直到发出关闭命令为止.这跟上一篇文章讨论到的
await
方法很类似.Listing2.2.The
HttpServer1
class'
await
method
Listing2.2中的
await
方法与前一篇文章的
await
方法的区别是,
Listing2.2中,request可以分发至
StaticResourceProcessor
或者
ServletProcessor
.如果URI包含字符串"
/servlet/
",request会被forward到后者.否则,request会被传给
StaticResourceProcessor
实例.

The
Request
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
方法,这些上篇文章讨论过.

The
Response
Class

Response
类实现了
javax.servlet.ServletResponse
.同样地,该类必须提供接口中的所有方法的实现.类似于
Request
类,我把所有方法都实现为空除了
getWriter
方法.
传给
PrintWriter
类构造器的第二个参数是一个Boolean,指示是否要启用
autoflush
.传递true作为第二个参数将使每次对
println
方法的调用都flush输出.但是,
print
调用不会flushoutput.因此,如果在servlet的
service
方法中的最后一行代码调用
print
方法,那么输出不会发送至浏览器.这个瑕疵将在后面的程序中得到修复.
Response
类也有一个我们在上一篇文章里讨论到的
sendStaticResource
方法.

The
StaticResourceProcessor
Class

StaticResourceProcessor
类用来处理请求静态资源的请求.它唯一的方法是
process
,如Listing2.3所示.Listing2.3.The
StaticResourceProcessor
class'
process
method
process
方法接收2个参数:一个
Request
实例和一个
Response
实例.它只简单的调用了一下
Response
类的
sendStaticResource
方法
.

The
ServletProcessor1
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
ServletProcessor1
class'
process
method
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上运行程序,需在工作路径下敲入如下命令:
Linux上,各包间使用冒号进行分隔.
要测试程序,在浏览器中敲入如下URL:
or
在你的浏览器中会看到如下文本:
注意你看不到第二个字符串(
Violetsareblue
)因为只有第一个字符串才flush到浏览器.HowTomcatWorks这本书的后面章节所附带的程序会给你演示如何解决这个问题.

Application2

在第一个程序里有一个严重的问题.在
ServletProcessor1
类的
process
方法,我们向上类型转换了
ex02.pyrmont.Request
to
javax.servlet.ServletRequest
的实例
,把它作为第一个参数传递给了servlet的
service
方法.我们也向上造型了
ex02.pyrmont.Response
to
javax.servlet.ServletResponse
的实例并把它作为第二个参数传递给了servlet的
service
方法.
这危及security.知道我们的servletcontainer内部工作机制的Servlet程序员能够向下造型
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
方法.servletprogrammer
ServletRequest
实例向下造型回
RequestFacade
;可是,他不能只访问
ServletRequest
接口中的可用方法.现在,
parseUri
方法已经安全了.Listing2.5展示了不完整的
RequestFacade
类.Listing2.5.The
RequestFacade
class
注意
RequestFacade
的构造器
.它接收一个
Request
对象但马上指派给了私有的
servletRequest
对象引用.也要注意
RequestFacade
类里的每个方法都调用了
ServletRequest
对象中的相应方法.在
ResponseFacade
类中也是如此.这些是在Application2中使用到的类:
HttpServer2
Request
Response
StaticResourceProcessor
ServletProcessor2
Constants
HttpServer2
类类似于
HttpServer1
,除了在
await
方法中它使用的是
ServletProcessor2
,而不是
ServletProcessor1
:
ServletProcessor2
类类似于
ServletProcessor1
,除了
process
方法中的如下这部分代码:

CompilingandRunningtheApplication

在工作目录敲入如下命令编译程序.
如果在Windows运行这个程序,typethefollowingcommandfromtheworkingdirectory:
Linux上,各包之间使用分号分隔.
你可以使用与Application1一样的URLs接收到同样的结果.

Summary

本文讨论了一个简单的Servlet容器,可以用来提供静态资源服务,也能处理像
PrimitiveServlet
这样简单的
servlet
.它也提供了
javax.servlet.Servlet
接口的背景信息.BudiKurniawanisaseniorJ2EEarchitectandauthor. 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息