第10章 Web应用
一个Web应用是由许多servlet、HTML页面、类和其他资源组成的集合,这些资源组成了一个运行在Web服务器上的完整应用程序。Web应用程序能够捆绑和运行在来自不同提供商的不同容器上。
10.1 Web服务器中的Web应用程序
在Web服务器中Web应用程序的根目录是一个特定的路径。例如,一个catalog应用,可以位于http://www.mycorp.com/catalog。以这个前缀开始的所有请求将被路由到代表catalog应用的ServletContext环境中。
servlet容器能够制定Web应用程序自动生成的规则。例如,一个~user/映射可用于映射到一个基于/home /user/public_html/的Web应用。
默认情况下,在任何时候一个Web应用程序的实例必须运行在一个虚拟机(VM)中。如果应用程序通过其部署描述符标记为“分布式”的,那么可以覆盖此行为。标记为分布式的应用程序必须遵守比普通的Web应用程序更严格的规则。本规范中陈述了这些规则。
10.2 与ServletContext的关系
servlet容器必须强制web应用程序和ServletContext之间一对一进行通信。ServletContext对象使用它的应用程序视图提供了一个servlet。
10.3 Web应用的要素
Web应用程序可能包括以下项目:
■ Servlets
■ JSP?页面
■ 实用工具类
■ 静态文件(HTML,图像,声音等)
■ 客户端Javaapplets,beans,和类
■ 结合上述所有要素的描述性的元信息
10.4 部署层次结构
本规范定义了一个用于部署和打包用途的,可存在于开放文件系统、归档文件或一些其他形式中的层次结构。建议servlet容器支持这种结构作为运行时表示形式,但不是必须的。
10.5 目录结构
一个Web应用程序以结构化的目录层次结构存在。层次结构的根目录作为文件的归档目录,这些文件是应用的一部分。例如,对于Web容器中一个Web应用程序的上下文路径/catalog,在Web应用程序层次结构中的index.html文件,或在WEB-INF/lib目录下的JAR文件中的META-INF/resources目录下包括的index.html文件,可以满足从/catalog/index.html送达的请求。如果在根上下文中和应用的WEB-INF/lib目录下的JAR文件中的META-INF/resources目录中都存在一个index.html文件,那么必须使用根上下文中的index.html。把URL匹配到上下文路径的规则安排在第12章:“请求映射到servlet”中。由于应用的上下文路径决定了Web应用内容的URL命名空间,Web容器必须拒绝Web应用定义的上下文路径,因为可能在这个URL命名空间中导致潜在的冲突。例如,试图部署两个具有相同上下文路径的Web应用时可能发生这种情况。由于把请求匹配到资源是区分大小写的,所以在确定潜在冲突时也必须区分大小写。
应用程序层次结构中存在一个名为“WEB-INF”的特殊目录。这个目录包含了与应用程序相关的所有东西,这些东西不在应用程序的归档目录中。大多数WEB-INF节点都不是应用程序公共文档树的一部分。除了静态资源和WEB-INF/lib目录下打包在JAR文件中META-INF/resources目录下的JSP文件之外,WEB-INF目录下包含的其他任何文件都不能由容器直接提供给客户端访问。然而,WEB-INF目录中的内容可以通过servlet代码调用ServletContext的getResource和getResourceAsStream方法来访问,并可使用RequestDispatcher调用公开这些内容。因此,如果应用开发人员想通过servlet代码访问这些内容,而不愿意直接对客户端公开应用程序指定配置信息,那么可以把它放在这个目录下。由于把请求匹配到资源的映射区分大小写,例如,客户端请求‘/WEB-INF/foo’,‘/WEb-iNf/foo’,不应该返回位于/WEB-INF下的Web应用程序的内容,也不应该返回其中任何形式的目录列表。
WEB-INF目录中的内容有:
■ /WEB-INF/web.xml部署描述文件。
■ servlet和实用工具类目录/WEB-INF/classes/。应用程序类加载器必须能够加载此目录中的类。
■ java归档文件区域/WEB-INF/lib/*.jar。这些文件中包括了servlet,beans,静态资源和打包在JAR文件中的JSP文件,以及其他对Web应用程序有用的实用工具类。Web应用程序的类加载器必须能够从这些归档文件中加载类。
Web应用程序类加载器必须先从WEB-INF/classes目录下加载类,然后从WEB-INF/lib目录下的JAR库中加载。此外,除了静态资源打包在JAR文件中的情况外,任何来自客户端的请求访问WEB-INF/目录中的资源必须返回一个SC_NOT_FOUND(404)的响应。
10.5.1 应用程序目录结构示例
下面是一个示例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/resources/catalog/moreOffers/books.html
/WEB-INF/classes/com/mycorp/servlets/MyServlet.class
/WEB-INF/classes/com/mycorp/util/MyUtils.class
10.6 Web应用程序归档文件
可以使用标准的Java归档工具把Web应用程序打包并签名到一个Web存档格式(WAR)文件中。例如,一个关于“issue tracking”的应用程序可以分布在一个称为issuetrack.war的归档文件中。
当打包成这种形式时,将生成一个META-INF目录,其中包含了对java归档工具有用的信息。尽管这个目录的内容可以通过servlet代码调用ServletContext的getResource和getResourceAsStream方法来访问,容器也不能把这个目录当作内容来响应客户端请求。此外,任何访问META-INF目录中的资源的请求必须返回一个SC_NOT_FOUND(404)的响应。
10.7 Web应用程序部署描述符
Web应用程序部署描述符(见第14章,“部署描述符”)的配置和部署信息包括以下几种类型:
■ ServletContext的初始化参数
■ Session配置
■ Servlet/JSP的定义
■ Servlet/JSP的映射
■ MIME类型映射
■ 欢迎文件列表
■ 错误页面
■ 安全
10.7.1 扩展的依赖关系
当许多应用程序使用相同的代码或资源时,通常将它们安装成容器的库文件。这些文件往往是通用的或标准的API,可以在不牺牲可移植性的情况下使用。仅由一个或几个应用程序使用的文件将作为Web应用程序的一部分来访问。容器必须为这些库提供一个目录。放置在这个目录中的文件必须对所有的Web应用可见。此目录的位置由容器指定。servlet容器用于加载这些库文件的类加载器必须和在同一个JVM中的所有Web应用的类加载器相同。这个类加载器的实例必须在Web应用程序类加载器的父类加载器链中。
为了保持可移植性,应用程序开发人员需要知道Web容器中安装了哪些扩展,而容器需要知道WAR中的servlet依赖哪些库。
依赖这种扩展的应用开发人员必须在WAR文件中提供一个列出所有WAR文件所需扩展的META-INF/MANIFEST.MF文件。清单文件的格式应该遵循标准的JAR清单格式。在部署Web应用程序期间,Web容器必须使正确的扩展版本对遵循可选包版本控制(Optional Package Versioning)机制(http://java.sun.com/j2se/1.4/docs/guide/extensions/)定义的规则的应用程序可见。
Web容器也必须能够识别出WAR文件中WEB-INF/lib目录下的任意一个JAR包中的清单文件声明的依赖关系。
如果Web容器不能够满足以这种方式声明的依赖关系,它应该使用一条有意义的错误消息拒绝该应用程序。
10.7.2 Web应用程序类加载器
容器用于加载WAR文件中servlet的类加载器必须允许开发人员使用getResource加载遵循正常JavaSE语义的WAR文件的JAR包中包含的任何资源。和Java EE许可协议中描述的一样,不属于Java EE产品的servlet容器不应该允许应用程序覆盖Java SE平台中的类,如在java.*和javax.*命名空间中Java SE不允许进行修改的类。容器不应该允许应用程序覆盖或访问容器的实现类。同时建议应用程序类加载器实现成WAR文件中的类和资源优先于属于容器范围内的JAR包中的类和资源加载。一个类加载器的实现也必须保证对部署到容器的每个web应用,调用Thread.currentThread.getContextClassLoader()返回一个实现了本节规定的约定的ClassLoader实例。此外,部署的每个Web应用程序的ClassLoader实例必须是一个单独的实例。容器必须在任何到Web应用程序的回调(包括监听器回调)之前设置上面描述的线程上下文ClassLoader,一旦回调返回,需要把它设置成原来的ClassLoader。
10.8 更新Web应用
服务器应该能够更新一个新版本的应用程序,而无需重启容器。当一个应用程序更新时,容器应提供一个可靠的方法来保存该应用程序的会话数据。
10.9 错误处理
10.9.1 请求属性
在发生错误时,Web应用程序必须能够详细说明,应用程序中的其他资源被用来提供错误响应的内容主体。这些资源的规定在部署描述符中配置。
如果错误处理器位于一个servlet或JSP页面:
■ 由容器创建的未包装过的请求和响应对象被传递给servlet或JSP页面。
■ 请求路径和属性被设置成如同已经完成RequestDispatcher.forward转发到错误资源一样。
■ 必须设置表10-1中的请求属性。
表10-1 请求属性和它们的类型
请求属性 |
类型 |
javax.servlet.error.status_code |
java.lang.Integer |
javax.servlet.error.exception_type |
java.lang.Class |
javax.servlet.error.message |
java.lang.Class |
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(像调用getRequestURI方法确定的URI一样)、以及发生错误的servlet的逻辑名称来生成专门的内容。
随着本规范的2.3版本引入了异常对象属性列表,异常类型和错误消息属性是多余的。保留他们是为了向后兼容早期的API版本。
10.9.2 错误页面
为了使开发人员能够在servlet产生一个错误时自定义内容的外观返回到Web客户端,部署描述符中定义了一组错误页面说明。这种语法允许当servlet或过滤器调用response的sendError方法指定状态码时,或如果servlet产生一个异常或错误传播给容器时,由容器返回资源配置。
如果调用了response的sendError方法,容器参照为Web应用声明的错误页面列表,使用状态码语法并试图匹配一个错误页面。如果找到一个匹配的错误页面,容器返回这个位置条目指示的资源。
在处理请求期间servlet或过滤器可能会抛出以下异常:
■ 运行时异常或错误
■ ServletException或它的子类异常
■ IOException或它的子类异常
Web应用程序可以使用exception-type元素声明错误页面。在这种情况下,容器通过比较抛出的异常与使用exception-type元素定义的error-page列表来匹配异常类型。在容器中的匹配结果返回这个位置条目指示的资源。在类层次中最接近的匹配将被返回。
如果声明的error-page中没有包含exception-type适合使用的类层次结构的匹配,那么抛出一个ServletException异常或它的子类异常,容器通过ServletException.getRootCause方法提取包装的异常。第二遍通过修改错误页面声明,使用包装的异常再次尝试匹配声明的错误页面。
使用exception-type元素声明的error-page在部署描述符中必须唯一的,由exception-type的类名决定它的唯一性。同样地, 使用status-code元素声明的error-page在部署描述符中必须是唯一的,由状态码决定它的唯一性。
当错误发生时,上面描述的错误页面机制不会干预使用RequestDispatcher或filter.doFilter方法调用。用这种方法,过滤器或Servlet有机会使用RequestDispatcher处理产生的错误。
如果上述错误页面机制没有处理servlet产生的错误,那么容器必须确保发送一个状态500的响应。
默认的servlet和容器将使用sendError方法,发送4xx和5xx状态的响应,这样错误机制才可能会被调用。默认的servlet和容器将使用setStatus方法,设置2xx和3xx的响应,并不会调用错误页面机制。
如果应用程序使用第2.3.3.3节,第2-10页“异步处理”中描述的异步操作,那么由应用程序负责处理应用程序创建的线程的所有错误。容器应该通过AsyncContext.start方法注意线程发出的错误。对于处理AsyncContext.dispatch过程中发生的错误,请参照第2-16页中看到的几节,“执行dispatch方法的时候可能发生的错误或异常必须被容器按照如下的方式捕获并处理”。
10.9.3 错误过滤器
错误页面机制运行在由容器创建的原来未包装过的或未经过过滤的request或response对象上。在第6.2.5节“过滤器和请求转发”中描述的机制可以在产生一个错误响应之前用来指定要应用的过滤器。
10.10 欢迎文件
Web应用程序开发人员可以在Web应用程序部署描述符中定义一个称为欢迎文件的局部URI有序列表。在Web应用程序部署描述符模式中描述了部署描述符中欢迎文件列表的语法。
这种机制的目的是,当一个对应到WAR文件中一个目录条目的请求URI没有映射到一个Web组件时,允许部署者为容器用于添加URI指定局部URI有序列表。这种请求被认为是有效的局部请求。
通过下面常见的例子来明确这种用法的便利:可以定义‘index.html’欢迎文件,以便像请求URLhost:port/webapp/directory/,其中‘directory’是WAR文件中的一个不能映射到servlet或JSP页面的条目,以下面的形式返回给客户端:‘host:port/webapp/directory/index.html’。
如果Web容器接收到一个有效的局部请求,Web容器必须检查部署描述符中定义的欢迎文件列表。欢迎文件列表是一个没有尾随或前导/的局部URL有序列表。Web服务器必须把部署描述符中按指定顺序的每个欢迎文件添加到局部请求,并检查WAR文件中的静态资源是否映射到请求URI。Web服务器必须再把部署描述符中按指定顺序的每个欢迎文件添加到局部请求,并检查servlet是否映射到请求URI。 Web容器必须将请求发送到WAR文件中第一个匹配的资源。容器可使用转发、重定向、或容器指定的机制将请求发送到欢迎资源,这与直接请求没有什么区别。
如果按上述的方式没有找到匹配的欢迎文件,容器可能会使用它认为合适的方式处理该请求。对于有的配置来说,这可能意味着返回一个目录列表,对其他配置来说可能返回一个404响应。
考虑一个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
■ 请求URI /foo将被重定向到URI /foo/。
■ 请求URI /foo/将返回/foo/index.html的。
■ 请求URI /catalog将被重定向到URI /catalog/。
■ 请求URI/catalog/将返回/catalog/default.jsp。
■ 请求URI/catalog/index.html将导致404未找到错误。
■ 请求URI/catalog/products将重定向到URI/catalog/products/。
■ 请求URI/catalog/products/将被传递给“默认”的servlet(如果有默认的servlet的话)。如果没有映射到“默认”的servlet,该请求可能会导致一个404未找到错误,可能会导致一个包括shop.jsp和register.jsp目录列表,或可能导致容器定义的其他行为。请参见12.2节,“映射规范”定义的“默认”servlet。
■ 所有上述的静态内容都可以打包到JAR文件的META-INF/resources目录中。这个JAR文件可以放到Web应用的WEB-INF/lib目录下。
10.11 Web应用程序环境
servlet容器不属于JavaEE技术标准的实现,鼓励实现这个容器但不是必需的,实现应用环境的功能请参见第15.2.2节中描述的“Web应用环境”和JavaEE规范。如果他们没有实现需要支持这种环境的条件,根据部署依赖它们的应用程序,容器应该提供一个警告。
10.12 Web应用程序部署
当一个Web应用程序部署到容器中,在Web应用程序开始处理客户端请求之前,必须按照下述步骤顺序执行。
■ 实例化部署描述符中<listener>元素标识的每个事件监听器的一个实例。
■ 对于已实例化的实现了ServletContextListener接口的监听器实例,调用contextInitialized()方法。
■ 实例化部署描述符中<filter>元素标识的每个过滤器的一个实例,并调用每个过滤器实例的init()方法。
■ 包含<load-on-startup>元素的<servlet>元素,根据load-on-startup元素值定义的顺序为每个servlet实例化一个实例,并调用每个servlet实例的init()方法。
10.13 包含web.xml部署描述符
如果Web应用不包含任何servlet、过滤器、或监听器组件可以不需要web.xml文件,或使用注解声明也一样。换句话说,只包含静态文件或JSP页面的应用程序并不需要一个web.xml的存在。