Servlet 工作原理解析.docx

上传人:b****5 文档编号:5750641 上传时间:2022-12-31 格式:DOCX 页数:17 大小:274.49KB
下载 相关 举报
Servlet 工作原理解析.docx_第1页
第1页 / 共17页
Servlet 工作原理解析.docx_第2页
第2页 / 共17页
Servlet 工作原理解析.docx_第3页
第3页 / 共17页
Servlet 工作原理解析.docx_第4页
第4页 / 共17页
Servlet 工作原理解析.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

Servlet 工作原理解析.docx

《Servlet 工作原理解析.docx》由会员分享,可在线阅读,更多相关《Servlet 工作原理解析.docx(17页珍藏版)》请在冰豆网上搜索。

Servlet 工作原理解析.docx

Servlet工作原理解析

Servlet工作原理解析

Web技术成为当今主流的互联网Web应用技术之一,而Servlet是JavaWeb技术的核心基础。

因而掌握Servlet的工作原理是成为一名合格的JavaWeb技术开发人员的基本要求。

本文将带你认识JavaWeb技术是如何基于Servlet工作,你将知道:

以Tomcat为例了解Servlet容器是如何工作的?

一个Web工程在Servlet容器中是如何启动的?

Servlet容器如何解析你在web.xml中定义的Servlet?

用户的请求是如何被分配给指定的Servlet的?

Servlet容器如何管理Servlet生命周期?

你还将了解到最新的Servlet的API的类层次结构,以及Servlet中一些难点问题的分析。

从Servlet容器说起

要介绍Servlet必须要先把Servlet容器说清楚,Servlet与Servlet容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。

虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。

从技术角度来说是为了解耦,通过标准化接口来相互协作。

既然接口是连接Servlet与Servlet容器的关键,那我们就从它们的接口说起。

前面说了Servlet容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。

例如现在比较流行的Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉Tomcat为例来介绍Servlet容器如何管理Servlet。

Tomcat本身也很复杂,我们只从Servlet与Servlet容器的接口部分开始介绍,关于Tomcat的详细介绍可以参考我的另外一篇文章《Tomcat系统架构与模式设计分析》。

Tomcat的容器等级中,Context容器是直接管理Servlet在容器中的包装类Wrapper,所以Context容器如何运行将直接影响Servlet的工作方式。

图1.Tomcat容器模型

从上图可以看出Tomcat的容器分为四个等级,真正管理Servlet的容器是Context容器,一个Context对应一个Web工程,在Tomcat的配置文件中可以很容易发现这一点,如下:

清单1Context配置参数

reloadable="true"/>

下面详细介绍一下Tomcat解析Context容器的过程,包括如何构建Servlet的过程。

Servlet容器的启动过程

Tomcat7也开始支持嵌入式功能,增加了一个启动类org.apache.catalina.startup.Tomcat。

创建一个实例对象并调用start方法就可以很容易启动Tomcat,我们还可以通过这个对象来增加和修改Tomcat的配置参数,如可以动态增加Context、Servlet等。

下面我们就利用这个Tomcat类来管理新增的一个Context容器,我们就选择Tomcat7自带的examplesWeb工程,并看看它是如何加到这个Context容器中的。

清单2.给Tomcat增加一个Web工程

Tomcattomcat=getTomcatInstance();

FileappDir=newFile(getBuildDirectory(),"webapps/examples");

tomcat.addWebapp(null,"/examples",appDir.getAbsolutePath());

tomcat.start();

ByteChunkres=getUrl("http:

//localhost:

"+getPort()+

"/examples/servlets/servlet/HelloWorldExample");

assertTrue(res.toString().indexOf("

HelloWorld!

")>0);

清单1的代码是创建一个Tomcat实例并新增一个Web应用,然后启动Tomcat并调用其中的一个HelloWorldExampleServlet,看有没有正确返回预期的数据。

Tomcat的addWebapp方法的代码如下:

清单3.Tomcat.addWebapp

publicContextaddWebapp(Hosthost,Stringurl,Stringpath){

silence(url);

Contextctx=newStandardContext();

ctx.setPath(url);

ctx.setDocBase(path);

if(defaultRealm==null){

initSimpleAuth();

}

ctx.setRealm(defaultRealm);

ctx.addLifecycleListener(newDefaultWebXmlListener());

ContextConfigctxCfg=newContextConfig();

ctx.addLifecycleListener(ctxCfg);

ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");

if(host==null){

getHost().addChild(ctx);

}else{

host.addChild(ctx);

}

returnctx;

}

前面已经介绍了一个Web应用对应一个Context容器,也就是Servlet运行时的Servlet容器,添加一个Web应用时将会创建一个StandardContext容器,并且给这个Context容器设置必要的参数,url和path分别代表这个应用在Tomcat中的访问路径和这个应用实际的物理路径,这个两个参数与清单1中的两个参数是一致的。

其中最重要的一个配置是ContextConfig,这个类将会负责整个Web应用配置的解析工作,后面将会详细介绍。

最后将这个Context容器加到父容器Host中。

接下去将会调用Tomcat的start方法启动Tomcat,如果你清楚Tomcat的系统架构,你会容易理解Tomcat的启动逻辑,Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《Tomcat的系统架构与设计模式,第二部分:

设计模式》。

Tomcat启动的时序图可以用图2表示。

图2.Tomcat主要类的启动时序图(查看大图)

上图描述了Tomcat启动过程中,主要类之间的时序关系,下面我们将会重点关注添加examples应用所对应的StandardContext容器的启动过程。

当Context容器初始化状态设为init时,添加在Contex容器的Listener将会被调用。

ContextConfig继承了LifecycleListener接口,它是在调用清单3时被加入到StandardContext容器中。

ContextConfig类会负责整个Web应用的配置文件的解析工作。

ContextConfig的init方法将会主要完成以下工作:

创建用于解析xml配置文件的contextDigester对象

读取默认context.xml配置文件,如果存在解析它

读取默认Host配置文件,如果存在解析它

读取默认Context自身的配置文件,如果存在解析它

设置Context的DocBase

ContextConfig的init方法完成后,Context容器的会执行startInternal方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:

创建读取资源文件的对象

创建ClassLoader对象

设置应用的工作目录

启动相关的辅助类如:

logger、realm、resources等

修改启动状态,通知感兴趣的观察者(Web应用的配置)

子容器的初始化

获取ServletContext并设置必要的参数

初始化“loadonstartup”的Servlet

Web应用的初始化工作

Web应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化主要是要解析web.xml文件,这个文件描述了一个Web应用的关键信息,也是一个Web应用的入口。

Tomcat首先会找globalWebXml这个文件的搜索路径是在engine的工作目录下寻找以下两个文件中的任一个org/apache/catalin/startup/NO_DEFAULT_XML或conf/web.xml。

接着会找hostWebXml这个文件可能会在System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default,接着寻找应用的配置文件examples/WEB-INF/web.xml。

web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。

如果当前应用支持Servlet3.0,解析还将完成额外9项工作,这个额外的9项工作主要是为Servlet3.0新增的特性,包括jar包中的META-INF/web-fragment.xml的解析以及对annotations的支持。

接下去将会将WebXml对象中的属性设置到Context容器中,这里包括创建Servlet对象、filter、listener等等。

这段代码在WebXml的configureContext方法中。

下面是解析Servlet的代码片段:

清单4.创建Wrapper实例

for(ServletDefservlet:

servlets.values()){

Wrapperwrapper=context.createWrapper();

StringjspFile=servlet.getJspFile();

if(jspFile!

=null){

wrapper.setJspFile(jspFile);

}

if(servlet.getLoadOnStartup()!

=null){

wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());

}

if(servlet.getEnabled()!

=null){

wrapper.setEnabled(servlet.getEnabled().booleanValue());

}

wrapper.setName(servlet.getServletName());

Mapparams=servlet.getParameterMap();

for(Entryentry:

params.entrySet()){

wrapper.addInitParameter(entry.getKey(),entry.getValue());

}

wrapper.setRunAs(servlet.getRunAs());

SetroleRefs=servlet.getSecurityRoleRefs();

for(SecurityRoleRefroleRef:

roleRefs){

wrapper.addSecurityReference(

roleRef.getName(),roleRef.getLink());

}

wrapper.setServletClass(servlet.getServletClass());

MultipartDefmultipartdef=servlet.getMultipartDef();

if(multipartdef!

=null){

if(multipartdef.getMaxFileSize()!

=null&&

multipartdef.getMaxRequestSize()!

=null&&

multipartdef.getFileSizeThreshold()!

=null){

wrapper.setMultipartConfigElement(new

MultipartConfigElement(

multipartdef.getLocation(),

Long.parseLong(multipartdef.getMaxFileSize()),

Long.parseLong(multipartdef.getMaxRequestSize()),

Integer.parseInt(

multipartdef.getFileSizeThreshold())));

}else{

wrapper.setMultipartConfigElement(new

MultipartConfigElement(

multipartdef.getLocation()));

}

}

if(servlet.getAsyncSupported()!

=null){

wrapper.setAsyncSupported(

servlet.getAsyncSupported().booleanValue());

}

context.addChild(wrapper);

}

这段代码清楚的描述了如何将Servlet包装成Context容器中的StandardWrapper,这里有个疑问,为什么要将Servlet包装成StandardWrapper而不直接是Servlet对象。

这里StandardWrapper是Tomcat容器中的一部分,它具有容器的特征,而Servlet为了一个独立的web开发标准,不应该强耦合在Tomcat中。

除了将Servlet包装成StandardWrapper并作为子容器添加到Context中,其它的所有web.xml属性都被解析到Context中,所以说Context容器才是真正运行Servlet的Servlet容器。

一个Web应用对应一个Context容器,容器的配置属性由应用的web.xml指定,这样我们就能理解web.xml到底起到什么作用了。

回页首

创建Servlet实例

前面已经完成了Servlet的解析工作,并且被包装成StandardWrapper添加在Context容器中,但是它仍然不能为我们工作,它还没有被实例化。

下面我们将介绍Servlet对象是如何创建的,以及如何被初始化的。

创建Servlet对象

如果Servlet的load-on-startup配置项大于0,那么在Context容器启动的时候就会被实例化,前面提到在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其定义了两个Servlet,分别是:

org.apache.catalina.servlets.DefaultServlet和org.apache.jasper.servlet.JspServlet它们的load-on-startup分别是1和3,也就是当Tomcat启动时这两个Servlet就会被启动。

创建Servlet实例的方法是从Wrapper.loadServlet开始的。

loadServlet方法要完成的就是获取servletClass然后把它交给InstanceManager去创建一个基于servletClass.class的对象。

如果这个Servlet配置了jsp-file,那么这个servletClass就是conf/web.xml中定义的org.apache.jasper.servlet.JspServlet了。

创建Servlet对象的相关类结构图如下:

图3.创建Servlet对象的相关类结构

初始化Servlet

初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单就是调用Servlet的init的方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet。

Tomcat容器为何要传StandardWrapperFacade给Servlet对象将在后面做详细解析。

如果该Servlet关联的是一个jsp文件,那么前面初始化的就是JspServlet,接下去会模拟一次简单请求,请求调用这个jsp文件,以便编译这个jsp文件为class,并初始化这个class。

这样Servlet对象就初始化完成了,事实上Servlet从被web.xml中解析到完成初始化,这个过程非常复杂,中间有很多过程,包括各种容器状态的转化引起的监听事件的触发、各种访问权限的控制和一些不可预料的错误发生的判断行为等等。

我们这里只抓了一些关键环节进行阐述,试图让大家有个总体脉络。

下面是这个过程的一个完整的时序图,其中也省略了一些细节。

图4.初始化Servlet的时序图(查看大图)

回页首

Servlet体系结构

我们知道JavaWeb应用是基于Servlet规范运转的,那么Servlet本身又是如何运转的呢?

为何要设计这样的体系结构。

图5.Servlet顶层类关联图

从上图可以看出Servlet规范就是基于这几个类运转的,与Servlet主动关联的是三个类,分别是ServletConfig、ServletRequest和ServletResponse。

这三个类都是通过容器传递给Servlet的,其中ServletConfig是在Servlet初始化时就传给Servlet了,而后两个是在请求达到时调用Servlet时传递过来的。

我们很清楚ServletRequest和ServletResponse在Servlet运行的意义,但是ServletConfig和ServletContext对Servlet有何价值?

仔细查看ServletConfig接口中声明的方法发现,这些方法都是为了获取这个Servlet的一些配置属性,而这些配置属性可能在Servlet运行时被用到。

而ServletContext又是干什么的呢?

Servlet的运行模式是一个典型的“握手型的交互式”运行模式。

所谓“握手型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这个交易过程直到这个交易完成为止。

这个交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置类。

所以对号入座,交易场景就由ServletContext来描述,而定制的参数集合就由ServletConfig来描述。

而ServletRequest和ServletResponse就是要交互的具体对象了,它们通常都是作为运输工具来传递交互结果。

ServletConfig是在Servletinit时由容器传过来的,那么ServletConfig到底是个什么对象呢?

下图是ServletConfig和ServletContext在Tomcat容器中的类关系图。

图6.ServletConfig在容器中的类关联图

上图可以看出StandardWrapper和StandardWrapperFacade都实现了ServletConfig接口,而StandardWrapperFacade是StandardWrapper门面类。

所以传给Servlet的是StandardWrapperFacade对象,这个类能够保证从StandardWrapper中拿到ServletConfig所规定的数据,而又不把ServletConfig不关心的数据暴露给Servlet。

同样ServletContext也与ServletConfig有类似的结构,Servlet中能拿到的ServletContext的实际对象也是ApplicationContextFacade对象。

ApplicationContextFacade同样保证ServletContex只能从容器中拿到它该拿的数据,它们都起到对数据的封装作用,它们使用的都是门面设计模式。

通过ServletContext可以拿到Context容器中一些必要信息,比如应用的工作路径,容器支持的Servlet最小版本等。

Servlet中定义的两个ServletRequest和ServletResponse它们实际的对象又是什么呢?

,我们在创建自己的Servlet类时通常使用的都是HttpServletRequest和HttpServletResponse,它们继承了ServletRequest和ServletResponse。

为何Context容器传过来的ServletRequest、ServletResponse可以被转化为HttpServletRequest和HttpServletResponse呢?

图7.Request相关类结构图

上图是Tomcat创建的Request和Response的类结构图。

Tomcat一接受到请求首先将会创建org.apache.coyote.Request和org.apache.coyote.Response,这两个类是Tomcat内部使用的描述一次请求和相应的信息类它们是一个轻量级的类,它们作用就是在服务器接收到请求后,经过简单解析将这个请求快速的分配

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1