分享

基于Spring MVC的Web应用开发(5) - Redirect

 windli笔记 2012-09-28

本文介绍Spring MVC中的重定向(Redirect),先回顾一下在JSP中,实现页面跳转的几种方式:

  1. RequestDispatcher.forward():是在服务端起作用,当使用forward()时,Servlet引擎传递http请求 从当前的servlet或者jsp到另外一个servlet,jsp或者普通的html文件,即你的表单(form)提交至a.jsp,在a.jsp中用 到了forward()重定向到b.jsp,此时form提交的所有信息在b.jsp都可以获得,参数自动传递,但forward()无法重定向至有 frame的jsp文件,可以重定向到有frame的html文件,同时forward()无法带参数传递,比如servlet?name=**,但可以 在程序内通过response.setAttribute("name",name)来将参数传至下一个页面。另外,重定向后浏览器地址栏的URL不变, 且通常在servlet中使用,不在jsp中使用。
  2. response.sendRedirect():时在用户的浏览器端工作,sendRedirect()可以带参数传递,比如 servlet?name=**传至下一个页面,同时它可以重定向至不同的主机,sendRedirect()可以重定向有frame的jsp文件。重定 向后在浏览器地址栏上会出现重定向页面的URL。另外,由于response是jsp页面中的隐含对象,故在jsp页面中可以用 response.sendRedirect()直接实现重定位。我们在讲第三点之前,先比较一下头两点,比较:(1) Dispatcher.forward()是容器中的控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;(2) response.sendRedirect()则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接可,这样,从浏览器的地址栏中可以看到跳 转后的链接地址。前者更加高效,再跑题一点,在有些情况下,比如,需要跳转到到一个其它服务器上的资源,则必须使用 HttpServletResponse.sendRequest()方法。
  3. <jsp:forward page=""/>:这个jsp标签的底层部分是由RequestDispatcher来实现的,因此它带有 RequestDispatcher.forward()方法的所有特性。要注意,它不能改变浏览器地址,刷新的话会导致重复提交。

在Spring MVC中 ,跳转其实和Controller中的return方法紧密联系在一起,编写一个新的Controller,叫RedirectController:

Java代码  收藏代码
  1. package org.springframework.samples.mvc.redirect;  
  2.   
  3. import org.joda.time.LocalDate;  
  4. import org.springframework.beans.factory.annotation.Autowired;  
  5. import org.springframework.core.convert.ConversionService;  
  6. import org.springframework.stereotype.Controller;  
  7. import org.springframework.web.bind.annotation.PathVariable;  
  8. import org.springframework.web.bind.annotation.RequestMapping;  
  9. import org.springframework.web.bind.annotation.RequestMethod;  
  10. import org.springframework.web.bind.annotation.RequestParam;  
  11. import org.springframework.web.servlet.mvc.support.RedirectAttributes;  
  12. import org.springframework.web.util.UriComponents;  
  13. import org.springframework.web.util.UriComponentsBuilder;  
  14.   
  15. @Controller  
  16. @RequestMapping("/redirect")  
  17. public class RedirectController {  
  18.       
  19.     private final ConversionService conversionService;  
  20.   
  21.     @Autowired  
  22.     public RedirectController(ConversionService conversionService) {  
  23.         this.conversionService = conversionService;  
  24.     }  
  25.   
  26.     @RequestMapping(value="/uriTemplate", method=RequestMethod.GET)  
  27.     public String uriTemplate(RedirectAttributes redirectAttrs) {  
  28.         redirectAttrs.addAttribute("account""a123");  // Used as URI template variable  
  29.         redirectAttrs.addAttribute("date"new LocalDate(20111231));  // Appended as a query parameter  
  30.         return "redirect:/redirect/{account}";  
  31.     }  
  32.   
  33.     @RequestMapping(value="/uriComponentsBuilder", method=RequestMethod.GET)  
  34.     public String uriComponentsBuilder() {  
  35.         String date = this.conversionService.convert(new LocalDate(20111231), String.class);  
  36.         UriComponents redirectUri = UriComponentsBuilder.fromPath("/redirect/{account}").queryParam("date", date)  
  37.                 .build().expand("a123").encode();  
  38.         return "redirect:" + redirectUri.toUriString();  
  39.     }  
  40.   
  41.     @RequestMapping(value="/{account}", method=RequestMethod.GET)  
  42.     public String show(@PathVariable String account, @RequestParam(required=false) LocalDate date) {  
  43.         return "redirect/redirectResults";  
  44.     }  
  45.   
  46. }  

先看看show这个方法:

Java代码  收藏代码
  1. @RequestMapping(value="/{account}", method=RequestMethod.GET)  
  2. public String show(@PathVariable String account, @RequestParam(required=false) LocalDate date) {  
  3.     return "redirect/redirectResults";  
  4. }  

注解好像有点多,简单解释一下每个注解代表什么含义,具体详细用法等后面讲到Spring MVC的Convert部分再展开。在@RequestMapping注解中有个/{account}的url模式,在方法中也有一个参数叫 accout,它带有@PathVariable注解,即当访问http://localhost:8080/web/redirect/1时,account变量会自动获取到1这个值(当然不限数字,只要是字符串即可),从PathVariable也可以猜到它是解析url路径的变量。再看@RequestParam注解,它是解析请求的参数的,如访问http://localhost:8080/web/redirect/1?date=2012/03/26时,show方法中的date参数会自动得到2012/03/26这个值并将其转换成LocalDate类。

最后看return,show方法返回String,并且没有加上@ResponseBody,返回的View就是该字符串对应的View名字,我们已经有一个默认的ViewResolver,因此这个@RequestMapping最终将返回到

webapp/WEB-INF/views/redirect/redirectResults.jsp:

Html代码  收藏代码
  1. <%@ taglib uri="http://java./jsp/jstl/core" prefix="c" %>  
  2. <%@ taglib uri="http://www./tags" prefix="spring" %>  
  3. <%@ page session="false" %>  
  4. <html>  
  5. <head>  
  6.     <title>Redirect Results</title>  
  7.     <link href="<c:url value="/resources/form.css" />rel="stylesheet"  type="text/css" />       
  8. </head>  
  9. <body>  
  10. <div class="success">  
  11.     <h3>Path variable 'account': ${account}</h3>  
  12.     <h3>Query param 'date': ${param.date}</h3>  
  13. </div>  
  14. </body>  
  15. </html>  

此jsp取得response中的account值(类似response.setAttribute(name,value)中设置的值),和 url参数传过来的变量date(注意${param.date}中的param是jsp标准中定义的8个隐含对象(是8个吧?))并显示在页面。

下面马上看到的RedirectController中另外两个方法将会展示如何跳转到上面的show方法的URL,先看:

Java代码  收藏代码
  1. @RequestMapping(value="/uriTemplate", method=RequestMethod.GET)  
  2. public String uriTemplate(RedirectAttributes redirectAttrs) {  
  3.     redirectAttrs.addAttribute("account""a123");  // Used as URI template variable  
  4.     redirectAttrs.addAttribute("date"new LocalDate(20111231));  // Appended as a query parameter  
  5.     return "redirect:/redirect/{account}";  
  6. }  

出现了一个新类RedirectAttributes,方法中给它添加了两个属性,添加的属性可以在跳转后的页面中获取到。最后该方法返回"redirect:/redirect/{account}",一般理解的跳转肯定是跳转到某个url,SpringMVC中也不例外,

它将跳转到http://localhost:8080/web/redirect/{account},而此URL最终将返回到一个JSP页面上。

看看浏览器效果,在浏览器访问 http://localhost:8080/web/redirect/uriTemplate,短暂的等待后,浏览器的地址栏将变成:

Html代码  收藏代码
  1. http://localhost:8080/web/redirect/a123?date=12%2F31%2F11  

提个问题,date=12%2F31%2F11是怎么回事?

看下一个方法:

Java代码  收藏代码
  1. @RequestMapping(value="/uriComponentsBuilder", method=RequestMethod.GET)  
  2. public String uriComponentsBuilder() {  
  3.     String date = this.conversionService.convert(new LocalDate(20111231), String.class);  
  4.     UriComponents redirectUri = UriComponentsBuilder.fromPath("/redirect/{account}").queryParam("date", date)  
  5.             .build().expand("a123").encode();  
  6.     return "redirect:" + redirectUri.toUriString();  
  7. }  

该方法只是前一个方法的变种。

访问http://localhost:8080/web/redirect/uriComponentsBuilder

短暂的等待后(应该非常短暂以至于你感觉不到),浏览器地址栏变成:

Html代码  收藏代码
  1. http://localhost:8080/web/redirect/a123?date=12/31/11  

效果一模一样(其实还是有点不一样,因为这个没有出现怪异的"%2F"了),打印redirectUri出来看看:

Java代码  收藏代码
  1. /redirect/a123?date=12/31/11  

那么最终return语句就是

Java代码  收藏代码
  1. return "redirect:/redirect/a123?date=12/31/11";  
UriComponents是一个工具类,帮助我们生成URL,比如在本例中,通过UriComponentsBuilder这个类,

有url为"/redirect/{account}",传递的参数为date,并且进行转码(encode),

结果返回的的URL就是"/redirect/a123?date=12/31/11"。(可以试着不加encode()方法看看效果)

这个结果也说明Spring MVC中的redirect可以带参数。

 

[本附录可不看,翻译也很渣]

附录Spring Reference Documentation中16.5.3 Redirection to views翻译

 

16.5.3 重定向(redirect)到视图(view)

前文提及,controller(控制器)返回一个view(视图)名,view resolver(视图解析器)解析这个特定的view。对于view technologies(视图技术?),象JSP(JSP由servlet或者JSP引擎解析),Spring MVC中的内部解决方案是将InternalResourceViewResolver和InternalResourceView组合起来使用,这种组 合采用一个内部定向(forward)或者通过Servlet自身API提供的RequestDispatcher.forward(..)方法或者 RequestDispatcher.include()方法。对于其它的view technologies,如Velocity,XSLT,等等,view本身就将内容直接写到response的输出流里了。

有时我们想要在view渲染前就将HTTP重定向回客户端,这是有可能的,比如,当使用POST方式提交数据到一个Controller 时,response实际上是另外一个controller的委托(比如一个成功的form表单提交)。在这种情况下,一个正常的内部定向意味着其它 controller将也会看到相同的POST数据,如果将它和其它期望的数据弄混了,这就是一个潜在的问题了。在显示结果之前进行重定向的另一个原因就 是排除用户多次提交表单数据的可能性。在这种场景下,浏览器会先发送一个初始POST;然后接受一个response来重定向到一个不同的URL;最后浏 览器的地址栏会体现在重定向response中的GET方式的URL。因此,从浏览器的角度看,当前页显示的不是POST而是GET的结果。最后一个影响 就是用户不能通过“意外地”点击了刷新,从而重复POST提交了相同的数据。刷新会到一个GET的结果页面,而不是重复以POST方式提交数据。(说实 话,这一段我没怎么看懂 =;=)

16.5.3.1 RedirectView

对controller来说,作为controller的response的结果来重定向的一个方式就是创建并返回一个Spring的 RedirectView的实例。在这种情况下,DispatcherServlet不使用通用的view解决机制。这是由于依然重定向的view已经有 了,DispatcherServlet只要简单得让这个view工作即可。

RedirectView通过HttpServletResponse.sendRedirect()实现,它作为一个HTTP重定向返回给客户端 浏览器。默认所有得model attribute会以URI模板变量得形式暴露在重定向的URL里。保留的attribute中那些primitive types或者collections/arrays的primitive types会自动地以查询参数的形式添加上。

如果一个model实例是为重定向特殊准备的,以查询参数添加primitive type attributes就是我们想要的结果。然而,在加上了注解的controller中,model可能包含额外的作为渲染目的的(rendering purposes)attribute(比如:drop-down field values)。为了避免这样的attribute出现在URL里,一个带有注解的controller可以声明一个 RedirectAttributes类型的参数,并使用它指定明确的attribute来让RedirectView获取到。如果controller 方法重定向了,RedirectAttributes的内容就可以使用了,否则使用的是model的内容。

注意来自当前请求的URI模板变量,在重定向到一个URL时,是自动可获得的,不需要额外添加,也不需要通过Model或者RedirectAttributes来添加,举个例子:

Java代码  收藏代码
  1. @RequestMapping(value = "/files/{path}", method = RequestMethod.POST)  
  2. public String upload(...) {  
  3.     // ...  
  4.     return "redirect:files/{path}";  
  5. }  

如果你使用RedirectView,这个view是被controller自身创建的,推荐你配置重定向URL,让其注入到 controller,从而it is not baked into the controller,而是在上下文中带着view名配置的。下一节我们继续讨论。

16.5.3.2 redirect:前缀

因为controller本身可以创建RedirectView,所以我们使用RedirectView可以工作得很好,不可避免的一个事实是 controller得知道这个Redirect,这让事情紧紧得耦合在一起了,controller不应该知道response是如何处理的,通常它应 该只关心注入进来的view名。

这个特殊的redirect:前缀可以达到这个目的,如果一个view名以前缀名redirect:方式返 回,UrlBasedViewrResolver(以及它的子类)会识别出来这是一个特殊的indication,这个indication 是一个redirect需要的。剩下的view名将会作为重定向的URL来处理。

如果controller返回的是RedirectView,实际的效果是一样的,但现在controller自身可以操作逻辑意义上的view 名,一个逻辑意义上的view名,如redirect:/myapp/some/resources会重定向关联到当前Servlet上下文,同时这样的 redirect:http:///some/arbitrary/path的view名会重定向到一个绝对路径的URL上。

16.5.3.3 forward:前缀

也可以给view名使用一个特殊的forward:前缀,它会被UrlBasedViewResolver及其子类解析。它给view名创建一个 InternalResourceView(实际使用RequestDispatcher.forward()),剩下的view名就是一个URL,然 而,这个前缀对于InternalResourceViewResolver和InternalResourceView(比如JSP)是不可用的。但是 这个前缀在你使用其它view technology时还是可以提供一些帮助的。但是仍然要强制forward到一个能被Servlet/JSP引擎处理的资源。(注意你也可以将多个 view解析器串起来,替代)。(说实话,这段没怎么明白 译者)

16.5.4 ContentNegotiatingViewResolver (跟我们讲的关联不大,略去 译者)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多