outuo2

 

Servlet规范简介

引言

Web 框架一般是通过一个 Servlet 提供统一的请求入口,将指定的资源映射到这个 servlet, 在这个 servlet 中进行框架的初始化配置,访问 Web 页面中的数据,进行逻辑处理后,将结果数据与的表现层相融合并展现给用户。 WEB 框架想要在符合 Servlet 规范的容器中运行,同样也要符合 Servlet 规范。

将一个 WEB 框架注入到一个 servlet 中,主要涉及到 Servlet 规范中以下部分:

Ø         部署描述符

Ø         映射请求到 Servlet

Ø         Servlet 生存周期

Ø         请求分发

 Servlet 相关技术规范简介

部署描述符

部署描述符就是位于 WEB 应用程序的 /WEB-INF 目录下的 web.xml XML 文件,是 WEB 应用程序不可分割的部分,管理着 WEB 应用程序的配置。部署描述符在应用程序开发人员,应用程序组装人员,应用程序部署人员之间传递 WEB 应用程序的元素和配置信息。

WEB 应用程序的部署描描述符中以下类型的配置和部署信息是所有的 servlet 容器必须支持的:

Ø         ServletContext 初始化参数

Ø         Session 配置

Ø        Servlet 声明

Ø        Servlet 映射

Ø        应用程序生存周期监听器

Ø         Filter 的定义和映射

Ø         MIME 类型的映射

Ø        欢迎文件列表

Ø        错误文件列表

出现在部署描述符中的安全信息可以不被支持,除非这个 Servlet 容器是 J2EE 规范实现的一部分。

所有正确的 WEB 应用程序部署描述符 (Servlet2.3 规范 ) 必须包含下面的 DOCTYPE 声明:

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web

Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

 

下面说明在部署描述符中是如何进行 Servlet 声明和映射的,这个 DTD 的全部内容可以在下面这个地址获得:

http://java.sun.com/dtd/web-app_2_3.dtd

在这个 DTD 中有关 Servlet 声明和映射和映射的部分如下:

<!--

The servlet element contains the declarative data of a

servlet. If a jsp-file is specified and the load-on-startup element

is present, then the JSP should be precompiled and loaded.

Used in: web-app

-->

<!ELEMENT servlet (icon?, servlet-name, display-name?, description?,

(servlet-class|jsp-file), init-param*, load-on-startup?, runas?,

security-role-ref*)>

<!--

The servlet-class element contains the fully qualified class name

of the servlet.

Used in: servlet

-->

<!ELEMENT servlet-class (#PCDATA)>

<!--

The servlet-mapping element defines a mapping between a servlet

and a url pattern

Used in: web-app

-->

<!ELEMENT servlet-mapping (servlet-name, url-pattern)>

<!--

The servlet-name element contains the canonical name of the

servlet. Each servlet name is unique within the web application.

Used in: filter-mapping, servlet, servlet-mapping

-->

<!ELEMENT servlet-name (#PCDATA)>

根据以上 DTD ,一个典型的 Servlet 的声明的格式如下:

<servlet>

<servlet-name>catalog</servlet-name>

<servlet-class>com.mycorp.CatalogServlet</servlet-class>

<init-param>

<param-name>catalog</param-name>

<param-value>Spring</param-value>

</init-param>

</servlet>

一个典型的Servlet映射如下:

<servlet-mapping>

<servlet-name>catalog</servlet-name>

<url-pattern>/catalog/*</url-pattern>

</servlet-mapping>

 

通过上面的方法,我们就声明了一个名称为 catalog Servlet,它的实现类为com.mycorp.CatalogServlet,并且带有一个catalog参数,参数值为Spring,所有向/catalog/*的请求都被映射到名称为catalogServlet

 

映射请求到 Servlet

收到一个请求后,WEB容器要确定转到哪一个WEB应用程序。被选择的应用程序的最长的上下文路径必须和请求的URL开始部分匹配。URL匹配的部分是映射到Servlet的上下文路径。

WEB 容器下一步必须按照下面的程序定位处理请求的Servlet

用来映射到Servlet的路径是请求对象的URL减去上下文的路径。下面的URL路径映射规则按顺序执行,容器选择第一个成功的匹配并且不在进行下一个匹配:

Ø        容器试着对请求的路径和Servlet的路径进行精确匹配,如果匹配成功则选择这个Servlet

Ø        容器会循环的去试着匹配最长的路径前缀:把’/’当作路径分隔符,按照路径树逐级递减的完成,选择最长匹配的Servlet

Ø        如果这个URL路径的最后有扩展名(比如.jsp,Servlet容器会试着匹配处理这个扩展名的Servlet

Ø        如果前面的没有与前面三条规则相匹配的Servlet,容器会试着为资源请求提供适当的资源,如果有“默认”的Servlet定义给这个应用程序,那么这个Servlet会被使用。

 

容器必须使用一个大小写敏感的匹配方式。

在部署描述符中,用下面的语法定义映射:

Ø        一个以’/’开始并且以’/*’结束的字符串用来映射路径。

Ø        一个以’*.’为前缀的字符串用来映射扩展名。

Ø        一个只包含’/’的字符串指示着这个应用程序“默认”的Servlet,在这种情况下,servlet的路径是请求的URI减去上下文路径,并且这个路径是null

Ø        所有其他的字符只用来精确匹配。

如果容器内置JSP容器,那么*.jsp被映射到这个容器,并允许JSP页面在需要的时候被执行。这种映射叫做隐含映射。如果WEB应用程序中定义了*.jsp的映射,那么这个映射有比隐含映射高的优先级。

WEB 容器允许显式的声明隐含映射以获得优先级,例如,*.shtml的隐含映射可以在服务器上被映射为包含功能。

映射实例:

path pattern

servlet

/foo/bar/*

servlet1

/baz/*

servlet2

/catalog

servlet3

*.bop

servlet4

下面是实际请求映射的结果

incoming path

servlet handling request

/foo/bar/index.html

servlet1

/foo/bar/index.bop

servlet1

/baz

servlet2

/baz/index.html

servlet2

/catalog

servlet3

/catalog/index.html

“default” servlet

/catalog/racecar.bop

servlet4

/index.bop

servlet4

请注意/catalog/index.html /catalog/racecar.bop这两种情况,因为是精确匹配,所以并没有映射到处理/catalogservlet

 

Servlet 生存周期

在介绍 Servlet 的生存周期之前需要先介绍一下 javax.servlet.Servlet 接口。所有的 Servlet 必须实现或者间接实现这个借口,我们通常可以通过继承 javax.servlet.GenericServlet 或者 javax.servlet.http.HttpServlet. 类来实现这个接口。

这个接口中定义了下面 5 种方法:

public void init(ServletConfig config);

public ServletConfig getServletConfig();

public void service(ServletRequest req, ServletResponse res);

public String getServletInfo();

public void destroy() ;

 

init() 方法

init 方法在容器器装入 Servlet 时执行, Servlet 容器在实例化后只调用一次 init 方法, init 方法必须在 servlet 接收到任何请求之前完成。

这个方法通常用来进行一些资源的管理和初始化,如从配置文件读取配置数据,读取初始化参数,初始化缓冲迟等一次性的操作。

getServletConfig() 方法

GetServletConfig 方法返回一个 ServletConfig 对象,该对象用来返回这个 Servlet 的初始化信息和启动参数。返回的是传递到 init 方法 ServletConfig

Service() 方法

Service 方法是应用程序逻辑的进入点,是 servlet 方法的核心, WEB 容器调用这个方法来响应进入的请求,只有 servlet 成功被 init() 方法初始化后, Service 方法才会被调用。

getServletInfo() 方法

这个方法返回一个字符串对象,提供有关 servlet 的信息,如作者、版本等。

destroy() 方法

destroy 方法在容器移除 Servlet 时执行,同样只执行一次。这个方法会在所有的线程的 service() 方法执行完成或者超时后执行,调用这个方法后,容器不会再调用这个 servlet 的方法,也就是说容器不再把请求发送给这个 Servlet       这个方法给 servlet 释放占用的资源的机会,通常用来执行一些清理任务。

 

这个接口定义了初始化一个 servlet, 服务请求和从容器中移除 servlet 的方法。他们按照下面的顺序执行:

1.         servlet 被实例化后,用 init 方法进行初始化

2.         客户端的任何请求都调用 service 方法

3.         servlet 被移除服务,调用 destroy 方法销毁

servlet 的生存周期如下图:
 

请求分发

请求分发可以让一个Servlet把请求分配到另外一个资源,RequestDispatcher接口提供了实现他的机制。可以通过下面两种方式从ServletContext中获得一个实现了RequestDispatcher接口的对象:

• getRequestDispatcher

• getNamedDispatcher

getRequestDispatcher方法接受一个指向目标资源的URL路径

RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”);

 

 

getNamedDispatcher方法接受一个Servlet名称参数,这个名称是在部署描述符中<servlet-name>元素指定的那个名称。

RequestDispatcher rd = getServletContext().getNamedDispatcher (“catalog”);

 

 

RequestDispatcher接口有两个方法,允许你在调用的servlet完成初步处理后把请求响应分配到另外一个资源,

forward()方法:

public void forward(ServletRequest request, ServletReponse reponse) throws SwerletException,IOException

forward方法上让你把请求转发到另外的Servlet或者jsp或者html等资源,由这个资源接下来负责响应。如:

RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”);

rd. forward(request,response);

 

include()方法:

public void include (ServletRequest request, ServletReponse reponse) throws SwerletException,IOException

include方法让你的Servlet响应中包含另外一个资源生成内容

RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”);

rd. include(request,response);

 

 

结合WebWork的具体分析

WebWork是由OpenSymphony组织开发实现MVC模式的J2EE Web框架。在介绍完servlet规范的相关内容后,我们看看WebWork是如何注入到一个Servlet中的,假设我们有一个上下文环境为“/WebWorkdDemo”的WEB应用。

部署描述符

在部署描述符中,我们需要进行如下配置:

<servlet>

<servlet-name>webwork</servlet-name>

<servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatcher</servlet-class>

</servlet>

……

<servlet-mapping>

<servlet-name>webwork</servlet-name>

<url-pattern>*.action</url-pattern>

</servlet-mapping>

我们声明了一个名为webworkServlet*.action到这个Servlet的映射,这个Servlet就是webwork中的controller,担任MVC框架中非常重要的控制器角色。

映射请求到Servlet

XWork的配置文件xwork.xml中有如下片段:

<action name="demo" class=" webworkapp.DemoAction">

    <result name="success" type="dispatcher">

       <param name="location">/demo.jsp</param>

    </result>

</action>

这样我们由http://localhost:8080/WebWorkDemo/demo.action这个URL向服务器发出请求时,WEB容器首先确定转到哪一个WEB应用程序,容器将请求URL和上下文环境进行匹配后知道将转到/WebWorkdDemo这个WEB应用。

接下来容器会在/WebWorkdDemo这个应用的部署描述符中进行查找处理这个请求的servlet,根据后缀*.action找到名称为webwork这个Servlet,这样根据部署描述符,这个请求被映射到webwork中的controller组件com.opensymphony.webwork.dispatcher.ServletDispatcher来处理。这个担任控制器组件的Servlet在他的service()方法中在根据请求的路径解析出对应的action来进行处理。

通过上面的的处理,实现了将web请求转到了webwork中的控制器ServletDispatcher。不止是webwork,实现MVCweb框架都需要进行类似的处理来将web请求转入到自己的controller.以便进行进一步的处理。

Servlet生存周期

ServletDispatcher这个Servlet的存周期可以如下:

1)      在服务器启动的时候,容器首先实例化ServletDispatcher

2)        实例化完成后,将调用init()方法,在init方法中执行了以下操作:

Ø         初始化Velocity引擎

Ø         检查是否支持配置文件重新载入功能。如果支持,每个request请求都将重新装载xwork.xml配置文件,在开发时非常方便。

Ø         设置一些文件上传的信息,比如:上传临时目录,上传的最大字节等。

3)      每次请求都调用service()方法,service方法中执行了以下方法

Ø         通过request请求取得action的命名空间

Ø         根据servlet请求的Path,解析出要调用该请求的Action的名字(actionName

Ø         创建Action上下文(extraContext),遍历HttpServletRequestHttpSessionServletContext 中的数据,并将其复制到WebworkMap实现中,至此之后,所有数据操作均在此Map结构中进行,从而将内部结构与Servlet API相分离。

Ø         以上述信息作为参数,调用ActionProxyFactory创建对应的ActionProxy实例。ActionProxyFactory 将根据Xwork 配置文件(xwork.xml)中的设定,创建ActionProxy实例,ActionProxy中包含了Action的配置信息(包括Action名称,对应实现类等等)。

Ø         执行proxyexecute()方法

4)      容器移除Servlet 时执行destroy(),在ServletDispatcher这个Servlet中并没有重写destroy方法,在移除Servlet时,将什么也不做。

请求分发

WebWork提供了多种活灵活视图展现方式,例如还是我们上面在xwork.xml中的配置:

<action name="demo" class=" webworkapp.DemoAction">

    <result name="success" type="dispatcher">

       <param name="location">/demo.jsp</param>

    </result>

</action>

根据以上配置当DemoAction的返回值为"success"时的处理类型为"dispatcher",当resulttype"dispatcher"时,通过javax.servlet.RequestDispatcherforward()include()方法将处理结果和表现层融合后展现给用户

我们可以看看WebWork提供的dispatcher类型Result Type的实现类com.opensymphony .webwork.dispatcher.ServletDispatcherResult中的代码片断:

  HttpServletRequest request = ServletActionContext.getRequest();

  HttpServletResponse response = ServletActionContext.getResponse();

  RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);

 

  if (dispatcher == null) {

    response.sendError(404, "result '" + finalLocation + "' not found");  

    return;

  }

 

  if (!response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) {

    request.setAttribute("webwork.view_uri", finalLocation);

    request.setAttribute("webwork.request_uri", request.getRequestURI());

   

    dispatcher.forward(request, response);

  } else {

    dispatcher.include(request, response);

  }

ServletDispatcherResult类的从ServletActionContex中得到HttpServletRequestHttpServletResponse,然后调用request.getRequestDispatcher(finalLocation)方法得到一个RequestDispatcher实例,如果返回的是null,则输出404页面未找到的错误,否则将调用dispatcher.forward(request, response)或者dispatcher.include(request, response)进行请求分发,将处理结果和表现层融合后展现给用户。

 

结束语

       通过以上的介绍,我们对web框架是如何注入到servlet中有了简单的了解,如果想更深入的研究,可以阅读Servlet规范以及一些成熟框架的源码。

posted on 2006-06-12 16:48 欧拓 阅读(6007) 评论(0)  编辑  收藏

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜