Spring重定向指南

参考原文:http://www.baeldung.com/spring-redirect-and-forward

概要

本文将重点介绍Spring中的重定向,并讨论每种策略背后的原因。

为什么要用重定向呢?

让我们先想一想,在Spring的应用中可能使用重定向的原因。当然或许有很多例子和原因,最简单的一个可能是POST提交表单数据,遇到多次提交数据问题,或者是将执行权转到另外一个控制器(Controller)的方法。

附注一点,典型的 Post / Redirect / Get 模式并不能充分解决双重提交问题 - 在初始提交完成之前刷新页面的问题可能仍然会导致双重提交。

RedirectView重定向

来,我们试试这种方式,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author Liu Hailin
* @create 2017-10-24 下午12:03
**/
@Controller
public class TestController {

@GetMapping("/test/redirectWithRedirectView")
public RedirectView redirectWithRedirectView(RedirectAttributes attributes){

attributes.addAttribute( "name","liuhailin" );
attributes.addAttribute( "age","17" );

return new RedirectView( "/redirectedUrl" );
}
}

在幕后,RedirectView将会触发执行HttpServletResponse.sendRedirect(),它将执行实际的重定向。

注意这里,我们是怎么把RedirectAttributes属性注入到方法里面的?是有Spring框架做了这个工作,让我们能够与这些属性交互。

我们添加一个模型熟悉,attribute,这个将会暴露在HTTP请求的URL上面,而且这个熟悉的值,只能是String或者可以转成String的对象。

测试一下,用简单的curl命令

1
curl -i http://localhost:8000/test/redirectWithRedirectView

结果

1
2
3
4
5
HTTP/1.1 302
Location: http://localhost:8000/redirectedUrl?name=liuhailin&age=17
Content-Language: zh-CN
Content-Length: 0
Date: Tue, 24 Oct 2017 04:07:48 GMT

注意:return new RedirectView( "/redirectedUrl" );这句与 return new RedirectView( "redirectedUrl" );的区别,加上/标识绝对路径,不加是相对路径,如果不加,上面返回结果

1
2
3
4
5
HTTP/1.1 302
Location: http://localhost:8000/test/redirectedUrl?name=liuhailin&age=17
Content-Language: zh-CN
Content-Length: 0
Date: Tue, 24 Oct 2017 04:07:21 GMT

可以看出是在/test下面。

“redirect:”前缀重定向

前面的方式是用RedirectView,因为几个原因,它不是最优的。

  • 首先,耦合Spring api,因为在代码里直接使用了RedirectView。

  • 其次,我们需要知道并不是像前面开始那样,实现所有控制器都总是需要重定向的,有的并不需要。

    一个更好的选择是使用redirect:前缀,这个视图名称,就像其它逻辑视图一样被注入到控制器中。控制器甚至都不知道重定向发生。

看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* @author Liu Hailin
* @create 2017-10-24 下午12:03
**/
@Controller
public class TestController {

@GetMapping("/test/redirectWithRedirectView")
public RedirectView redirectWithRedirectView(RedirectAttributes attributes){

attributes.addAttribute( "name","liuhailin" );
attributes.addAttribute( "age","17" );

return new RedirectView( "/redirectedUrl" );
}



@GetMapping("/test/redirectWithRedirectPrefix")
public ModelAndView redirectWithUsingRedirectPrefix(ModelMap model) {
model.addAttribute("sex", "man");
return new ModelAndView("redirect:/redirectedUrl", model);
}

}

当返回一个用redirect:前缀开头的视图名时,UrlBasedViewResolver(及所有的子类)识别这需要进行重定向。然后剩下的部分将会作为重定向的URL。

说明重要的一点:return new ModelAndView("redirect:/redirectedUrl", model);里面redirect:/redirectedUrl/与不加/的区别。

如果需要重定向到一个绝对URL,我们可以使用像这样的名称: redirect: http://localhost:8080/spring-redirect/redirectedUrl

curl测试一下

1
curl -i http://localhost:8000/test/redirectWithRedirectPrefix

结果

1
2
3
4
5
HTTP/1.1 302
Location: http://localhost:8000/redirectedUrl?sex=man
Content-Language: zh-CN
Content-Length: 0
Date: Tue, 24 Oct 2017 07:20:59 GMT

如果不加/

结果

1
2
3
4
5
HTTP/1.1 302
Location: http://localhost:8000/test/redirectedUrl?sex=man
Content-Language: zh-CN
Content-Length: 0
Date: Tue, 24 Oct 2017 07:21:56 GMT

“forward:”前缀转发

我们现在看看做一点稍微的不同的事儿,”forward”转发

在看代码之前,我们先来看一下 对转发与重定向的语义的快速、高度概括

  • 重定向将以包含302响应码和Location头的新URL进行响应;然后浏览器/客户端将再次向新的URL发出请求
  • 转发完全在服务器端发生; Servlet容器将相同的请求转发到目标URL;浏览器中的URL无须改变

现在我们看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author Liu Hailin
* @create 2017-10-24 下午12:03
**/
@Controller
public class ForwardController {

@GetMapping("/forwardWithForwardPrefix")
public ModelAndView redirectWithUsingForwardPrefix(ModelMap model) {
model.addAttribute( "name", "liuhailin" );
return new ModelAndView( "forward:/redirectedUrl", model );
}

}

redirect:一样,forward:也由UrlBasedViewResolver 和其子类解析,在内部,这将创建一个InternalResourceView,它为新视图执行一个RequestDispatcher.forward()操作。

RedirectAttributes属性

接下来 - 让我们看看 在一个重定向中传递属性 - 充分利用框架中的RedirectAttribures:

1
2
3
4
5
6
7
@GetMapping("/redirectWithRedirectAttributes")
public RedirectView redirectWithRedirectAttributes(RedirectAttributes attributes) {

attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectAttributes");
attributes.addAttribute("attribute", "redirectWithRedirectAttributes");
return new RedirectView("redirectedUrl");
}

如前所述,我们可以直接在方法中插入属性对象 - 这使得该机制非常容易使用。

还要注意,我们也 添加一个Flash属性 - 这是一个不会被添加到URL中的属性。我们可以通过这种属性来实现——我们稍后可以在重定向的最终目标的方法中使用@ModelAttribute(“flashAttribute”)来访问flash属性:

1
2
3
4
5
6
7
8
@GetMapping("/redirectedUrl")
public ModelAndView redirection(
ModelMap model,
@ModelAttribute("flashAttribute") Object flashAttribute) {

model.addAttribute("redirectionAttribute", flashAttribute);
return new ModelAndView("redirection", model);
}

因此,圆满完工——如果你需要使用curl测试该功能:

1
curl -i http://localhost:8080/spring-rest/redirectWithRedirectAttributes

我们将会被重定向到新的位置:

1
2
3
4
5
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=4B70D8FADA2FD6C22E73312C2B57E381; Path=/spring-rest/; HttpOnly
Location: http://localhost:8080/spring-rest/redirectedUrl;
jsessionid=4B70D8FADA2FD6C22E73312C2B57E381?attribute=redirectWithRedirectAttributes

这样,使用RedirectAttribures代替ModelMap,赋予我们仅在重定向操作中涉及的 两种方法之间共享一些属性 的能力。

没有前缀的另一种配置

现在让我们探索另一种配置——没有前缀的重定向。

为了实现这一点,我们需要使用 org.springframework.web.servlet.view.XmlViewResolver

1
2
3
4
5
6
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/spring-views.xml</value>
</property>
<property name="order" value="0" />
</bean>

代替我们在之前配置里使用的 org.springframework.web.servlet.view.InternalResourceViewResolver

1
2
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>

我们还需要在配置里面定义一个 RedirectView bean:

1
2
3
<bean id="RedirectedUrl" class="org.springframework.web.servlet.view.RedirectView">
<property name="url" value="redirectedUrl" />
</bean>

现在我们可以通过id来引用这个新的bean来触发重定向:

1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("/")
public class RedirectController {

@GetMapping("/redirectWithXMLConfig")
public ModelAndView redirectWithUsingXMLConfig(ModelMap model) {
model.addAttribute("attribute", "redirectWithXMLConfig");
return new ModelAndView("RedirectedUrl", model);
}
}

为了测试它,我们再次使用 *curl *命令:

1
curl -i http://localhost:8080/spring-rest/redirectWithRedirectView

结果会是:

1
2
3
4
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location:
http://localhost:8080/spring-rest/redirectedUrl?attribute=redirectWithRedirectView

重定向HTTP POST请求 Request

对于类似银行付款这样的用例,我们可能需要重定向HTTP POST请求。根据返回的HTTP状态码,POST请求可以重定向到HTTP GET或POST上。

根据HTTP 1.1协议 参考 ,状态码301(永久移除)和302(已找到)允许请求方法从POST更改为GET。该规范还定义了不允许将请求方法从POST更改为GET的相关的307(临时重定向)和308(永久重定向)状态码。

现在,我们来看看将post请求重定向到另一个post请求的代码:

1
2
3
4
5
@PostMapping("/redirectPostToPost")
public ModelAndView redirectPostToPost(HttpServletRequest request) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.TEMPORARY_REDIRECT);
return new ModelAndView("redirect:/redirectedPostToPost");
}
1
2
3
4
@PostMapping("/redirectedPostToPost")
public ModelAndView redirectedPostToPost() {
return new ModelAndView("redirection");
}

现在,让我们使用curl命令来测试下重定向的POST:

1
curl -L --verbose -X POST http://localhost:8080/spring-rest/redirectPostToPost

我们正在被重定向到目标地址:

1
2
3
4
5
6
7
8
9
10
11
> POST /redirectedPostToPost HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 08 Aug 2017 07:33:00 GMT

{"id":1,"content":"redirect completed"}

结论

本文介绍了 在Spring中实现重定向的三种不同方法 ,在执行这些重定向时如何处理/传递属性以及如何处理HTTP POST请求的重定向。

坚持技术分享,您的支持将鼓励我继续创作!