一个Web应用是一个Web服务器上众多资源的集合,它包括了servlets,HTML页面,类,和其它组成一个完整应用的资源。Web应用可以被捆绑和运行在来自多个供应商的多种容器上。
一、在Web Servers里面的Web应用
在一个Web服务器中,一个Web应用把一个指定的路径作为根目录。比如,一个分类应用能够位于"http://www.mycorp.com/catalog"。
所有以此为前缀的请求都会被路由到代表分类应用的ServletContext。
一个servlet能够为Web应用的自动构建建立规则。比如,~user/映射能够被用来映射到一个位于/home/user/public_html/ 的Web应用。
默认地,在任意时刻一个Web应用的一个时刻必须运行在一个VM上面。如果这个应用通过它的描述符被标记为分布式的,那么这个行为能够被重写。被标记为分布式的应用必须比不同Web应用遵循更多限制规则。
二、与ServletContext的关系
一个servlet容器必须在一个Web应用和一个ServletContext之间确保一对一的对应关系。一个ServletContext对象提供一个有应用视图的servlet。
三、一个Web应用的元素
一个Web应用必须包含下列项目:
- Servlets
- JSP页面
- Utility Classes
- 静态文件(HTML,Image,sounds,etc.)
- 客户端Java applets,beans和类
- 黏合上述元素的描述符元信息
四、部署层级
这份规范定义了一个用来部署和打包的层次结构,这种层次结构能够存在一个开放文件系统中,一个归档文件中或者其它形式中。
五、目录结构
一个Web应用以一个结构化的层次目录存在。这个层次的根作为属于应用一部分的文件的文档根目录。
比如,对于一个Web容器中用/catalog上下文路径的一个Web应用,在Web应用层次的基目录,或者在一个META-INF/resources目录下包含index.html,WEB-INF/lib中的JAR文件中的index.html可以用来满足来自/catalog/index.html的请求。如果一个index.html文件同时出现在根上下文中和应用的WEB-INF/lib目录里的JAR文件中的META-INF/resources目录里,那么根上下文中的文件必须被使用。匹配URLs到上下文路径的规则在章节Mapping Requests Servlets中。由于应用的上下文路径决定了Web应用的内容的的URL命名空间,web容器必须拒绝定义了会在URL命名空间中引起潜在冲突的上下文路径的应用。比如,通过用相同上下文路径部署第二个Web应用就会出现这种情况。由于请求会按照大小写敏感方式被映射到资源,潜在冲突的决定也必须对大小写敏感。
一个特殊目录存在于名字为WEB-INF的应用层级中。这个目录包含了所有不在应用文档根目录的应用相关的东西。大多数WEB-INF 节点并不是应用的公开文档树的一部分。除了静态资源和打包在WEB-INF/lib目录中的JAR文件中META-INF/resources中的JSPs,包含在WEB-INF目录中的其它文件不会被容器直接暴露给客户端。然而,WEB-INF目录的内容对ServletContext上使用getResource和getResourceAsStream方法调用的servlet代码可见,并且可以使用RequestDispatcher方法调用来暴露。因此,如果应用开发者需要从servlet代码中访问应用那些不直接暴露给客户端的详细配置信息,他可以把它们放到这个目录下面。由于请求会以大小写敏感的方式被映射到资源,比如,客户端对/WEB-INF/foo,/WEb-iNf/foo的请求不应该获得位于/WEB-INF目录下以及任何形式的目录列表的Web应用的内容。
WEB-INF目录的内容如下:
- /WEB-INF/web.xml部署描述符
- 放servlet和utility classes的/WEB-INF/classes/目录。这个目录下的类必须能让应用的类加载器访问到。
- 放JAVA归档文件的/WEB-INF/lib/.jar*的区域。这些文件包含servlets,beans,打包在JAR中的JSPs和静态资源,以及对Web应用有用的utility类。Web应用的类加载器必须能够从任何归档文件中加载类。
Web应用类加载器必须先从WEB-INF/classes加载类,然后从WEB-INF/lib目录中的JARs中加载类。除了打包在JAR文件中的静态资源,来自客户端去访问WEB-INF/目录里资源的任何请求必须返回一个带404状态码的response。
-
应用目录结构的例子
下面是示例Web应用中所有文件的列表:/index.html /howto.jsp /feedback.jsp /images/banner.gif /images/jumping.gif /WEB-INF/web.xml /WEB-INF/lib/jspbean.jar /WEB-INF/lib/catalog.jar!/META-INF/resouces/catalog/moreOffers/books.html /WEB-INF/classes/com/mycorp/servlets/MyServlet.class /WEB-INF/classes/com/mycorp/util/MyUtils.class
六、Web应用归档文件
Web应用能使用标准的Java归档工具被打包进Web ARchive format(WAR)文件中。比如,对于问题跟踪的一个应用可能被分布在叫做issuetrack.war的归档文件中。
当打包进这种形式中,将会出现一个META-INF目录,它包含了对Java 归档工具很有用的信息。这个目录一定不能被容器直接作为内容来响应一个Web客户端的请求,尽管它的内容可以通过ServletContext上的getResource和getResourceAsStream调用对servlet代码可见。任何访问META-INF目录下的资源的请求必须返回带404状态码的response。
七、Web应用部署描述符
Web应用部署描述符包含下列配置类型和部署信息:
- ServletContext初始化参数
- Session Configuration
- Servlet/JSP定义
- Servlet/JSP映射
- MIME类型映射
- Welcome File list
- Error Pages
- Security
-
扩展上的依赖
当很多应用使用相同的代码或者资源时,它们通常会在容器中被安装为库文件。这些文件很通用或者是无需考虑兼容性就能使用的标准APIs。被一个或者多个应用使用的文件将会作为Web应用的一部分被访问。容器必须为这些库文件提供一个目录。这个目录中的文件必须要在多个Web应用之间被访问到。这个目录的位置根据具体的容器而定。servlet容器用来加载这些库文件的类加载器对同一个JVM中所有的Web应用都应该一样。这个类加载器实例必须在Web应用类加载器的父加载器链的某个位置上。
应用开发者需要知道哪些扩展要被安装在一个Web容器中,并且容器需要知道WAR中哪些依赖servlets有这些库,以便保留兼容性。
依赖这个扩展的应用开发者必须在WAR文件中提供一个META-INF/MANIFEST.MF入口,这个入口列出了WAR需要的所有扩展。manifest入口的格式应该遵循标准JAR manifest格式。在Web应用的部署期间,Web容器必须遵循下列规则,给应用配上正确的扩展版本。
Web容器必须能够识别这样的声明依赖,它们被写在WAR里面WEB-INF/lib 目录入口下的任意类库JARs的manifest入口里。
如果一个Web容器不能够满足这种方式里面声明的依赖,它应该用一个错误信息来拒绝这种应用。
-
Web应用类加载器
容器使用来从WAR里加载一个servlet的类加载器必须允许开发者使用getResource并遵循普通Java SE机制来加载WAR里面的库JARs里的任意资源。如Java EE license协议描述的,不是Java EE产品一部分的servlet容器不应该允许应用来重写Java SE平台不允许被修改的类,比如在java.和javax.命名空间里面的类。容器不应该允许应用来重写或者访问容器的实现类。
推荐实现应用类加载器以便WAR中的类和资源优先于容器端库JARs里面的类和资源。一个实现必须确保对于每一个部署在一个容器中的web应用,对Thread.currentThread.getContextClassLoader()的调用必须返回一个实现了本章具体协议的的ClassLoader实例。更进一步说,ClassLoader实例必须对于每个部署的web应用必须是一个单独的实例。容器需要在任何回调函数之前设置线程上下文ClassLoader到一个web应用中。
八、替换一个Web应用
一个服务器用一个新版本来替换一个应用,而无需重启容器。当一个应用被替换,容器应该提供一个鲁棒的方法来保留应用中的会话数据。
九、错误处理
-
请求属性
一个Web应用必须能够详细指出错误何时出现,应用中的其它资源被用来提供错误响应的内容体。这些资源的详情配置在部署描述符中。
如果错误处理的问题是错误处理器是一个servlet或者一个JSP页面:
- 被容器创建的未包装过的原始request和response对象被传递给这个servlet或者JSP页面。
- 如果RequestDispatcher.forward已经被执行,请求路径和属性被设置给错误资源。
- Table中的请求属性必须被设置:
Table 请求属性和它们的类型
Request Attribute | Type |
---|---|
javax.servlet.error.status_code | java.lang.Integet |
javax.servlet.error.exception_type | java.lang.Class |
javax.servlet.error.message | java.lang.String |
javax.servlet.error.exception | java.lang.Throwable |
javax.servlet.error.request_uri | java.lang.String |
javax.servlet.error.servlet_name | java.lang.String |
这些属性允许servlet根据状态码,异常类型,错误信息,异常对象传递,出现错误的servlet的请求的URI,以及出现错误的servlet的逻辑名字产生具体的内容。
把异常对象引到2.3版本的规范的属性列表中,异常类型和错误信息属性就是多余的了。它们被保留,是为了对以前版本的API做向后兼容。
-
错误页面
当一个servlet出现一个错误,为了允许开发者定制返回给Web客户端的内容外观,部署描述符定义一个错误页面描述的列表。当一个servlet或者filter在response上为一个具体错误码调用sendError时,或者这个servlet产生一个传递给容器的异常或者错误时,这个语法允许资源的配置被容器返回。
如果sendError方法在response上被调用,容器会查找为Web应用使用状态码语法和尝试匹配声明的错误页面列表。如果有一个匹配的,容器返回匹配位置入口表明的资源。
一个servlet或者filter可能在一个请求期间抛出下列异常:
- 运行时异常或者错误
- ServletException或者其子类
- IOException或者其子类
Web应用可以使用exception-type元素声明错误页面。这种情况下,容器通过对比抛出的异常和使用exception-type 定义的错误页面列表来匹配异常。一个匹配会让容器返回在位置入口指明的资源。类层次结构中最近的匹配会生效。
如果没有包含一个exception-type的error-page声明符合使用类层次匹配,并且抛出的异常是一个ServletException或者其子类,容器获取被包装过的异常,如被ServletException.getRootCause方法定义的一样。在错误页面声明上做第二次通过,再次尝试匹配错误页面声明,但是使用包装过的异常。
在部署描述符中使用exception-type元素的错误页面异常对exception-type的类名必须唯一。类似的,使用status-code*元素的错误页面在部署描述符中对于状态码必须唯一。
当使用RequestDispatcher或者filter.doFilter方法调用时,上述错误页面机制不会干涉错误何时出现。这种方式下,一个使用RequestDispatcher的filter或者servlet有机会处理出现的错误。
如果一个servlet产生了一个没有被上述错误页面机制处理的错误时,容器必须确保发送一个状态码为500的response。
默认的servlet和容器将会使用sendError方法发送4xx和5xx状态的response,所以错误机制可以被调用。默认servlet和容器将会为2xx何3xx的responses使用setStatus,并且不会调用错误页面机制。
如果应用正在使用上述Asynchronous processing的异步操作,应用负责处理应用创建的线程中的所有错误。容器可以通过AsyncContext.start处理线程中的错误。为了处理AsyncContext.dispatch期间的出现的错误,参考“在dispatch方法执行期间可能出现的任何错误或异常必须被容器捕获和处理”。
-
错误Filters
错误页面机制在容器创建的未包装/未过滤的request和response对象上起作用。Filters和RequestDispatcher可以用来在一个错误response生成之前指明被应用的filters。
十、欢迎文件
Web应用开发者能在Web应用部署描述符中定义一个叫做欢迎文件的部分URIs的有序列表。对这个列表的部署描述符语法会在下述Web应用部署描述符机制中说明。
当有一个请求WAR文件的目录入口对应的URI没有被映射到Web组件,这个机制的目的就是允许开发者为容器指定一个用来追加到URIs的部分URIs的有序列表。
请参考下列通用示例:
一个欢迎文件index.html能够被定义,以便对host:port/webapp/directory/ 的请求会返回host:port/webapp/directory.index.html 给客户端。上述的directory是WAR中的一个入口,而没有被映射到一个servlet或者JSP页面。
如果一个Web容器接收一个合法的部分请求,Web容器必须检查定义在部署描述符中的欢迎文件列表。欢迎文件列表是一个没有尾部或前导'/'的部分URIs的有序列表。Web服务器必须把每一个欢迎文件按照部署描述符中的顺序追加到部分请求,并且检查WAR中的静态资源是否被映射到那个请求URI。如果没有找到映射,Web服务器必须再次把每个欢迎文件按照部署描述符中的顺序追加到部分请求,并且检查这个servlet是否被映射到那个请求URI。Web容器必须把请求发送到WAR中第一个匹配到的资源。容器可能把请求发送到欢迎资源,方式或者是forward,或者是redirect,或者是区别于直接请求的一个容器详细机制。
如果按照上述方式没有找到匹配的欢迎文件,容器可以按照它能找到的合适方式去处理请求。对于一些配置,这意味着返回一个目录列表或者对另外一些配置就返回404的response。
考虑这样一个Web应用:
- 部署描述符列出了下列欢迎文件
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list> - WAR中的静态内容
/foo/index.html
/foo/default.jsp
/foo/orderform.html
/foo/home.gif
/catalog/default.jsp
/catalog/products/shop.jsp
/catalog/products/register.jsp - /foo 将会被重定向到/foo/
- /foo/ 将会被重定向到/foo/index.html
- /catalog 将会被重定向到/catalog/
- /catalog/ 将会被重定向到/catalog/default.jsp
- /catalog/index.html 将会引发一个404 not found
- /catalog/products 将会被重定向到/catalog/products/
- /catalog/products/会被传递给默认servlet,如果有默认servlet。如果没有匹配的默认servlet,请求可能引发一个404 not found,也可能引发一个包含shop.jsp 和register.jsp** 的目录列表,或者可能引发被容器定义的其它行为。
- 上述所有的静态内容能够和上述打包在jar文件META-INF/resources目录里列出的内容一起被打包进JAR文件中。
十一、Web应用环境
不属于Java EE兼容实现的Servlet容器被提倡(但不是必须)实现"Web Application Environment and Java EE specification"中描述的应用环境功能。如果它们没有实现要求支持这个环境的功能,在部署依赖它们的应用时,容器应该发出一个警告。
十二、Web应用部署
当一个web应用被部署到一个容器中,在web应用开始处理客户端请求之前,下列步骤必须按顺序执行。
- 给每个在部署描述符中<listener>元素定义的事件监听器实例化一个实例。
- 为实现了ServletContextListener的监听者实例,调用contextInitialized()方法。
- 给部署描述符中<filter>元素定义的每一个filter实例化一个实例,并调用每个filter实例的init()方法。
十三、一个web.xml部署描述符的总结
如果一个web应用不包含任何Servlet,Filter,或者监听组件,或者用注解声明的相同组件,那么这个应用不要求包含一个web.xml。换句话说,一个仅包含静态文件或者JSP页面的应用不要求一个web.xml。
翻译自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010