Servlet 3.0 之 分发请求
当构建一个Web应用,把一个请求的处理转给其它servlet,或者在response中包含其它servlet的输出通常很有用处。RequestDispatcher接口提供一种机制来完成这个功能。
当在请求上启动了异步,AsyncContext允许用户把请求分发回servlet容器。
一、获取一个RequestDispatcher
实现了RequestDispatcher接口的对象可以通过ServletContext中的下列方法被获取:
- getRequestDispatcher
- getNamedDispatcher
getRequestDispatcher方法需要传入一个字符串参数,这个参数描述了ServletContext范围内的一个路径。这个路劲必须相对ServletContext的根目录,并且以"/"开头或者为空。这个方法根据 Mapping Requests to Servlets 章节里servlet路径匹配规则,使用这个路径来查找一个servlet,使用一个RequestDispatcher 对象来包装它,并且返回一个结果对象。如果基于给定的路径没有servlet能够被解析,提供它的RequestDispatcher返回那个路径的内容。
getNamedDispatcher方法需要传入一个字符串参数,这个参数用来表示ServletContext已知的一个servlet的名字。如果一个servlet被发现,它会被用一个RequestDispatcher对象来包装并且返回这个对象。如果没有servlet与指定的名字关联,方法必须返回null。
为了使RequestDispatcher对象能够通过使用相对于当前请求(不是相对ServletContext的根路径)路径的相对路径被获得,ServletRequest 接口中提供了getRequestDispatcher 方法。
这个方法的行为与ServletContext 中有相同名字的方法类似。servlet容器使用请求对象中的信息把给定的相对于当前servlet的路径转换为一个完整路径。比如,在一个"/"根上下文,一个请求/garden/tools.html中,通过ServletRequest.getRequestDispatcher("header.html") 获得的请求分发器将像调用ServletContext.getRequestDispatcher("/garden/header.html") 一样。
-
请求分发器路径中的查询字符串
使用路径信息来创建RequestDispatcher对象的ServletContext和ServletRequest方法允许请求字符串信息的可选附件给这个路径。比如,一个开发者可以通过下列代码获得一个RequestDispatcher:String path = "/raisins.jsp?orderno=5"; RequestDispatcher rd = context.getRequestDispatcher(path); rd.include(request, response);
被用来创建RequestDispatcher的请求字符串中指定的参数相比传递给servlet的有相同名字的参数有更高优先级。与一个RequestDispatcher 关联的参数仅被应用在include和forward 调用期间。
二、使用一个请求分发器
为了使用一个请求分发器,一个servlet调用RequestDispatcher 接口的include或者forward方法。给这些方法的参数可能是通过javax.servlet接口的service方法传递进来的request和response参数,或者是request或response包装对象类的子类实例。对于后者,包装器实例必须包装容器传递给service方法的request或者response对象。
容器提供者应该确保请求一个目标servlet的分发出现在相同JVM里的相同线程。
三、Include方法
RequestDispatcher接口的include方法可以在任意时间被调用。include方法的目标servlet可以获得请求对象的所有方面,它对response对象的使用会更加受限。
它仅能够把信息写到response对象的ServletOutputStream或者Writer,并且通过写内容超出response缓存,或者显示调用ServletResponse接口的flushBuffer方法来提交response。
除了HttpServletRequest.getSession()和HttpServletRequest.getSession(boolean)方法,它不能设置头部或者调用任何影响response头部的方法。
任何尝试设置头部必须被忽略,并且如果response已经被提交,任何对需要添加一个Cookie响应头的HttpServletRequest.getSession()或者HttpServletRequest.getSession(boolean)的调用必须抛出一个IllegalStateException。
如果默认servlet是RequestDispatch.include()的目标,并且请求的资源并不存在,那么这个默认的servlet必须抛出FileNotFoundException。如果这个异常没有被抓住和处理,并且response还没有被提交,这个状态码必须被设置为500。
-
Included Request 参数
除了用getNameDispatcher方法获得的servlets,一个被另外servlet使用RequestDispatcher接口的include方法调用的servlet可以访问它被调用的路径。
下列请求属性必须被设置: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
这些属性可以通过请求对象上从included servlet通过getAttribute方法访问到,并且它们的值必须分别等于请求URI,context path,servlet path,path info和included servlet的查询字符串。如果这个请求是后续included,这些属性会被这个include代替。
如果这个included servlet通过getNameDispatcher方法被获得,这些属性一定不能被设置。
四、Forward 方法
仅当输出还未被提交到客户端,RequestDispatcher接口的forward方法可以通过调用servlet而被调用。如果response缓存中的输出数据还未被提交,在目标servlet的service方法被调用之前,其中的内容必须被清除。如果response对象已经被提交,IllegalStateException必须被抛出。
暴露给目标servlet的请求对象的路径元素必须反应用来获取RequestDispatcher的路径。
对此,唯一的例外就是如果RequestDispatcher通过getNamedDispatcher方法获得。这种场景下,请求对象的路径元素必须反应出原始请求的路径。
在RequestDispatcher接口的forward方法没有异常并返回之前,response内容必须被容器发送,提交和关闭。如果一个错误出现在RequestDispatcher.forward()的目标中,异常会在所有调用filters和servlets之中传递,最终传递回容器。
-
查询字符串
当forwarding或者including请求时,请求分发机制负责聚合查询字符串参数。 -
Forwarded请求参数
除了通过getNamedDispatcher方法获得的servlets,一个被另外servlet通过使用RequestDispatcher的forward方法调用的servlet能够访问原始请求的路径。
下列请求属性必须被设置: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,这些方法在传递给来自客户端的请求的调用链上第一个servlet对象的请求对象上被调用。
这些属性通过请求对象上的getAttribute方法从forwarded servlet访问到。需要注意的是,这些属性必须总能反应原始请求里面的信息,即使在多个forwards和后续请求被调用的情况下。
如果forwarded servlet通过使用getNamedDispatcher方法获得,那么这些属性一定不能被设置。
五、错误处理
如果是一个请求分发器目标的servlet抛出一个运行时异常或者一个ServletException或者IOException类型的已检查异常,它会被传递到发起调用的servlet。所有其它异常应该被包装为ServletException,并且把异常的根本原因设置为原始异常,就像它没有被传递一样。
六、获得一个AsyncContext
实现AsyncContext接口的一个对象可以通过一个startAsync方法从ServletRequest获取。一旦你有一个AsyncContext,你能用它通过complete()方法或者一个dispatch方法的使用来完成请求的处理。
七、Dispatch Method
下列来自AsyncContext的方法能够用来分发请求:
- dispatch(path)
dispatch方法需要一个字符串参数,用来描述ServletContext范围里面的路径。这个路径必须相对于ServletContext的根路径,并且以'/'开头。 - dispatch(servletContext, path)
dispatch方法需要一个字符串参数,用来描述指定ServletContext范围里面的路径。这个路径必须相对于指定ServletContext的根路径,并且以'/'开头。 - dispatch()
dispatch方法不需要参数。它使用原始URI作为路径。如果AsyncContext通过startAsync(ServletRequest,ServletResponse)初始化,并且传递进来的请求是HttpServletRequest的实例,那么这个分发就是HttpServletRequest.getRequestURI()返回的URI。否则,当它是容器的最后一个分发,这次分发就是请求的URI。
一个AsyncContext接口的dispatch方法可以被等待异步事件发生的应用调用。如果complete()已经在AsyncContext上被调用,必须抛出一个IllegalStateException异常。所有dispatch方法立即返回并且并不提交response。
暴露给目标servlet的请求对象的路径元素必须要反应出AsyncContext.dispatch里面指定的路径。
-
查询字符串
当分发请求时,请求分发机制负责聚合查询字符串。 -
Dispatched 请求参数
一个通过使用AsyncContext接口的dispatch方法调用的servlet可以访问到原始请求的路径。
下列请求属性必须被设置:javax.servlet.async.request_uri javax.setvlet.async.context_path javax.servlet.async.servlet_path javax.servlet.async.path_info javax.servlet.async.query_string
这些属性的值必须分别等于HttpServletRequest接口的这些方法返回的值-getRequestURI, getContextPath, getServletPath, getPathInfo, getQueryString,这些方法在传递给来自客户端的请求的调用链上第一个servlet对象的请求对象上被调用。
这些属性通过请求对象上的getAttribute方法从forwarded servlet访问到。需要注意的是,这些属性必须总能反应原始请求里面的信息,即使在多个dispatches被调用的情况下。
翻译自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010