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") 一样。

  1. 请求分发器路径中的查询字符串
    使用路径信息来创建RequestDispatcher对象的ServletContextServletRequest方法允许请求字符串信息的可选附件给这个路径。比如,一个开发者可以通过下列代码获得一个RequestDispatcher

     String path = "/raisins.jsp?orderno=5";
     RequestDispatcher rd = context.getRequestDispatcher(path);
     rd.include(request, response);
    

被用来创建RequestDispatcher的请求字符串中指定的参数相比传递给servlet的有相同名字的参数有更高优先级。与一个RequestDispatcher 关联的参数仅被应用在includeforward 调用期间。

二、使用一个请求分发器

为了使用一个请求分发器,一个servlet调用RequestDispatcher 接口的include或者forward方法。给这些方法的参数可能是通过javax.servlet接口的service方法传递进来的requestresponse参数,或者是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。

  1. 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之中传递,最终传递回容器。

  1. 查询字符串
    当forwarding或者including请求时,请求分发机制负责聚合查询字符串参数。

  2. Forwarded请求参数
    除了通过getNamedDispatcher方法获得的servlets,一个被另外servlet通过使用RequestDispatcherforward方法调用的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里面指定的路径。

  1. 查询字符串
    当分发请求时,请求分发机制负责聚合查询字符串。

  2. 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容