引言
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/*的请求都被映射到名称为catalog的Servlet。
映射请求到
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这两种情况,因为是精确匹配,所以并没有映射到处理/catalog的servlet。
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> |
我们声明了一个名为webwork的Servlet和*.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,实现MVC的web框架都需要进行类似的处理来将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),遍历HttpServletRequest、HttpSession、ServletContext 中的数据,并将其复制到Webwork的Map实现中,至此之后,所有数据操作均在此Map结构中进行,从而将内部结构与Servlet API相分离。
Ø 以上述信息作为参数,调用ActionProxyFactory创建对应的ActionProxy实例。ActionProxyFactory 将根据Xwork 配置文件(xwork.xml)中的设定,创建ActionProxy实例,ActionProxy中包含了Action的配置信息(包括Action名称,对应实现类等等)。
Ø 执行proxy的execute()方法
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",当result的type为"dispatcher"时,通过javax.servlet.RequestDispatcher的forward()或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中得到HttpServletRequest和HttpServletResponse,然后调用request.getRequestDispatcher(finalLocation)方法得到一个RequestDispatcher实例,如果返回的是null,则输出404页面未找到的错误,否则将调用dispatcher.forward(request, response)或者dispatcher.include(request, response)进行请求分发,将处理结果和表现层融合后展现给用户。
结束语
通过以上的介绍,我们对web框架是如何注入到servlet中有了简单的了解,如果想更深入的研究,可以阅读Servlet规范以及一些成熟框架的源码。