9. 分派请求
构建 Web 应用时,把请求转发给另一个 servlet 处理、或在 response 中包含另一个 servlet 的输出通常是很有
用的。 RequestDispatcher
接口提供了一种机制来实现这种功能。
当请求启用异步处理时, AsyncContext
允许用户将这个请求转发到 servlet 容器。
9.1 获得一个 RequestDispatcher
实现了 RequestDispatcher
接口的对象,可以从 ServletContext
中的下面方法得到:
getRequestDispatcher
getNamedDispatcher
getRequestDispatcher
方法需要一个 String 类型的参数描述在 ServletContext
作用域内的路径。这个路径必须是相对于 ServletContext
的根路径,或以 /
开头,或者为空。该方法根据这个路径使用 servlet 路径匹配规则(见第 12 章,请求映射到 servlet)来查找 servlet,把它包装成 RequestDispatcher
对象并返回。如果基于给定的路径没有找到相应的 servlet,那么返回这个路径内容提供的 RequestDispatcher
。
getNamedDispatcher
方法使用一个 ServletContext
知道的 servlet 名称作为参数。如果找到一个 servlet,则把它包装成 RequestDispatcher
对象,并返回该对象。如果没有与给定名字相关的 servlet,该方法必须返回 null
。
为了让 RequestDispatcher
对象使用相对于当前请求路径的相对路径(不是相对于 ServletContext
根路径)获得一个 servlet,在 ServletRequest
接口中提供了 getRequestDispatcher
方法。
此方法的行为与 ServletContext
中同名的方法相似。 Servlet 容器根据 request 对象中的信息把给定的相对路
径转换成当前 servlet 的完整路径。例如,在以 /
作为上下文根路径和请求路径/garden/tools.html
中,通过
ServletRequest.getRequestDispatcher("header.html")
获 得 的 请 求 调 度 器 和 通 过 调 用
ServletContext.getRequestDispatcher("/garden/header.html")
获得的完全一样。
9.1.1 请求调度器路径中的查询字符串
ServletContext
和 ServletRequest
中创建 RequestDispatcher
对象的方法使用的路径信息中允许附加可选的查询字符串信息。比如,开发人员可以通过下面的代码来获得一个 RequestDispatcher
:
String path = “/raisins.jsp?orderno=5”;
RequestDispatcher rd = context.getRequestDispatcher(path);
rd.include(request, response);
查询字符串中指定的用来创建 RequestDispatcher
的参数优先于传递给它包含的 servlet 中的其他同名参数。
与 RequestDispatcher
相关的参数作用域仅适用于包含(include
)或转发(forward
)调用期间。
9.2 请求调度器的使用
要使用请求调度器, servlet 可调用 RequestDispatcher
接口的 include
或 forward
方法。这些方法的参数既可以是 javax.servlet.Servlet
接口的 service
方法传来的 request 和 response 对象实例,也可以是本规范的 2.3 版 本中介绍的 request 和 response 包装器类的子类对象实例。对于后者,包装器实例必须包装容器传递到 service
方法中的 request 和 response 对象。
容器提供者应该保证分发到目标 servlet 的请求作为原始请求发生在的同一个 JVM 的同一个线程中。
9.3 Include 方法
RequestDispatcher
接口的 include
方法可能随时被调用。 include
方法的目标 servlet 能够访问 request 对象的各个方法( all aspects),但是使用 response 对象的方法会受到更多限制。
它只能把信息写到 response 对象的 ServletOutputStream
或 Writer
中,或提交在最后写保留在 response 缓冲区中的内容,或通过显式地调用 ServletResponse
接口的 flushBuffer
方法。它不能设置响应头部信息或调用任何影响响应头部信息的方法, HttpServletRequest.getSession()
和 HttpServletRequest.getSession(boolean)
方法除 外 。 任 何 试 图 设 置 头 部 信 息 必 须 被 忽 略 , 任 何 调 用HttpServletRequest.getSession()
和 HttpServletRequest.getSession(boolean)
方法将需要添加一个 Cookie 响应头部信息,如果响应已经提交,必须抛出一个 IllegalStateException
异常。
如果默认的 servlet 是 RequestDispatch.include()
的目标 servlet,而且请求的资源不存在,那么默认的 servlet 必须抛出 FileNotFoundException
异常。如果这个异常没有被捕获和处理,以及响应还未提交,则响应状态码必须被设置为 500
。
9.3.1 内置请求参数
除了可以用 getNamedDispatcher
方法获得 servlet 外,已经被另一个 servlet 使用 RequestDispatcher
的 include
方法调用过的 servlet,有权访问被调用过的 servlet 的路径。
以下的 request 属性必须被设置:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
这些属性可以通过包含的 servlet 的 request 对象的 getAttribute
方法访问,它们的值必须分别与被包含 servlet 的请求 RUI、上下文路径、 servlet 路径、路径信息、查询字符串相等。如果随后的请求包含这些属性,那么这些属性会被后面包含的属性值替换。
如果包含的 servlet 通过 getNamedDispatcher
方法获得,那么这些属性不能被设置。
9.4 Forward 方法
RequestDispatcher
接口的 forward
方法,只有在没有输出提交到向客户端时,通过正在被调用的 servlet 调用。如果 response 缓冲区中存在尚未提交的输出数据,这些数据内容必须在目标 servlet 的 service
方法调用前清除。如果 response 已经提交,必须抛出一个 IllegalStateException
异常。
request 对象暴露给目标 servlet 的路径元素(path elements)必须反映获得 RequestDispatcher
使用的路径。唯一例外的是,如果 RequestDispatcher
是通过 getNamedDispatcher
方法获得。这种情况下, request 对象的路径元素必须反映这些原始请求。
在 RequestDispatcher
接口的 forward
方法无异常返回之前,响应的内容必须被发送和提交,且由 Servlet 容器关闭,除非请求处于异步模式。如果 RequestDispatcher.forward()
的目标发生错误,异常信息会传回所有调用它经过的过滤器和 servlet,且最终传回给容器。
9.4.1 查询字符串
在转发或包含请求时请求调度机制负责聚集( aggregating)查询字符串参数。
9.4.2 转发的请求参数
除了可以用 getNamedDispatcher
方法获得 servlet 外, 已经被另一个 servlet 使用 RequestDispatcher
的 forward
方法调用过的 servlet,有权访问被调用过的 servlet 的路径。
以下的 request 属性必须设置:
javax.servlet.forward.request_uri
javax.servlet.forward.context_path
javax.servlet.forward.servlet_path
javax.servlet.forward.path_info
javax.servlet.forward.query_string
这些属性的值必须分别与 HttpServletRequest
的 getRequestURI
,、 getContextPath
、 getServletPath
、 getPathInfo
、getQueryString
方法的返回值相等,这些方法在从客户端接收到的 request 对象上调用,值传递给调用链中的第一个 servlet 对象。(在 request 对象上调用从客户端接收请求的调用链中的第一个 servlet 对象)
这些属性通过转发 servlet 的 request 对象的 getAttribut
方法访问。请注意,即使在多个转发和相继的包含( subsequent includes)被调用的情况下,这些属性必须始终反映原始请求中的信息。
如果转发的 servlet 使用 getNamedDispatcher
方法获得,这些属性必须不能被设置。
9.5 错误处理
如果请求分发的目标 servlet 抛出运行时异常或受检查类型异常 ServletException
或IOException
,异常应该传播到调用的 servlet。所有其它的异常都应该被包装成ServletExceptions
,异常的根本原因设置成原来的异常,因为它不应该被传播。
9.6 获得一个异步上下文对象
实现了 AsyncContext
接口的对象可从 ServletRequest
的一个 startAsync
方法中获得,一旦有了 AsyncContext
对象,你就能够使用它的 complete()
方法来完成请求处理,或使用下面描述的转发方法。
9.7 Dispatch
方法
可以使用 AsyncContext
中下面的方法来转发请求:
-
dispatch(path)
这个 dispatch 方法的 String 参数描述了一个在
ServletContext
作用域中的路径。这个路径必须是相对于
ServletContext
的根路径并以/
开头。 -
dispatch(servletContext, path)
这个 dispatch 方法的 String 参数描述了一个在
ServletContext
指定作用域中的路径。这个路径必须是相对于ServletContext
的根路径并以开头。
-
dispatch()
这个方法没有参数,它使用原来的 URI 路径。如果
AsyncContext
已经通过startAsync(ServletRequest, ServletResponse)
初始化,且传递过来的 request 是HttpServletRequest
的实例,那么这个请求分发到
HttpServletRequest.getRequestURI()
返回的 URI。否则当最后转发时当由容器转发到 request 的 URI。AsyncContext
接口中的dispatch
方法可被等待异步事件发生的应用程序调用。如果AsyncContext
已经调用了complete()
方法,必须抛出IllegalStateException
异常。所有不同的 dispatch 方法会立即返回并且不会提交 response。request 对象暴露给目标 servlet 的路径元素( path elements)必须反映
AsyncContext.dispatch
中指定的路径。
9.7.1 查询字符串
请求调度机制是在调度请求时负责聚焦( aggregating)查询字符串。
9.7.2 调度请求参数
使用 AsyncContext
的 dispatch
方法调用过的 servlet 能够访问原始请求的路径。
下面的 request 属性必须设置:
javax.servlet.async.request_uri
javax.servlet.async.context_path
javax.servlet.async.servlet_path
javax.servlet.async.path_info
javax.servlet.async.query_string
这些属性的值必须分别与 HttpServletRequest
的 getRequestURI
、 getContextPath
、 getServletPath
、 getPathInfo
、getQueryString
方法的返回值相等,这些方法在从客户端接收到的 request 对象上调用,值传递给调用链中的第一个 servlet 对象。
这些属性通过转发 servlet 的 request 对象的 getAttribut
方法访问。请注意,即使在多个转发和相继的包含( subsequent includes)被调用的情况下,这些属性必须始终反映原始请求中的信息。