A Guide To Spring Redirects – Spring重定向指南

最后修改: 2015年 7月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This tutorial will focus on implementing a Redirect in Spring and will discuss the reasoning behind each strategy.

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

2. Why Do a Redirect?

2.为什么要做重定向?

Let’s first consider the reasons why we may need to do a redirect in a Spring application.

让我们首先考虑一下我们可能需要在Spring应用程序中进行重定向的原因

There are many possible examples and reasons of course. For example, we might need to POST form data, work around the double submission problem or just delegate the execution flow to another controller method.

当然,有很多可能的例子和原因。例如,我们可能需要POST表单数据,解决重复提交的问题,或者只是将执行流程委托给另一个控制器方法。

A quick side note here: The typical Post/Redirect/Get pattern doesn’t adequately address double submission issues, and problems such as refreshing the page before the initial submission has completed may still result in a double submission.

这里有一个简单的附带说明。典型的Post/Redirect/Get模式并不能充分解决重复提交的问题,在初次提交完成之前刷新页面等问题仍然可能导致重复提交。

3. Redirect With the RedirectView

3.用RedirectView重定向

Let’s start with this simple approach and go straight to an example:

让我们从这个简单的方法开始,直接进入一个例子

@Controller
@RequestMapping("/")
public class RedirectController {
    
    @GetMapping("/redirectWithRedirectView")
    public RedirectView redirectWithUsingRedirectView(
      RedirectAttributes attributes) {
        attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectView");
        attributes.addAttribute("attribute", "redirectWithRedirectView");
        return new RedirectView("redirectedUrl");
    }
}

Behind the scenes, RedirectView will trigger an HttpServletResponse.sendRedirect(), which will perform the actual redirect.

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

Notice here how we’re injecting the redirect attributes into the method. The framework will do the heavy lifting and allow us to interact with these attributes.

注意这里我们是如何将重定向属性注入方法中的。框架将做繁重的工作,并允许我们与这些属性互动。

We’re adding the model attribute attribute, which will be exposed as HTTP query parameter. The model must contain only objects — generally Strings or objects that can be converted to Strings.

我们正在添加模型属性attribute,它将作为HTTP查询参数暴露出来。模型必须只包含对象–一般是字符串或可以转换为字符串的对象。

Let’s now test our redirect with the help of a simple curl command:

现在让我们用一个简单的curl命令来测试我们的重定向

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

And here’s our result:

这就是我们的结果。

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

4. Redirect With the Prefix redirect:

4.使用前缀redirect:重定向

The previous approach — using RedirectView — is suboptimal for a few reasons.

之前的方法–使用RedirectView–是次优的,有几个原因。

First, we’re now coupled to the Spring API because we’re using the RedirectView directly in our code.

首先,我们现在与Spring API耦合了,因为我们在代码中直接使用了RedirectView

Second, we now need to know from the start, when implementing that controller operation, that the result will always be a redirect, which may not always be the case.

第二,我们现在需要从一开始就知道,在实现该控制器操作时,其结果将总是一个重定向,但情况可能并不总是如此。

A better option is using the prefix redirect:. The redirect view name is injected into the controller like any other logical view name. The controller is not even aware that redirection is happening.

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

Here’s what that looks like:

这看起来像什么。

@Controller
@RequestMapping("/")
public class RedirectController {
    
    @GetMapping("/redirectWithRedirectPrefix")
    public ModelAndView redirectWithUsingRedirectPrefix(ModelMap model) {
        model.addAttribute("attribute", "redirectWithRedirectPrefix");
        return new ModelAndView("redirect:/redirectedUrl", model);
    }
}

When a view name is returned with the prefix redirect:, the UrlBasedViewResolver (and all its subclasses) will recognize this as a special indication that a redirect needs to happen. The rest of the view name will be used as the redirect URL.

当视图名称以redirect:为前缀返回时, UrlBasedViewResolver(及其所有子类)将识别出这是一个需要重定向的特殊指示。视图名称的其余部分将被用作重定向的URL。

A quick but important note is that when we use this logical view name here — redirect:/redirectedUrl — we’re doing a redirect relative to the current Servlet context.

一个快速但重要的注意点是,当我们在这里使用这个逻辑视图名称–redirect:/redirectedUrl–我们在做一个重定向相对于当前的Servlet上下文。

We can use a name such as a redirect: http://localhost:8080/spring-redirect-and-forward/redirectedUrl if we need to redirect to an absolute URL.

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

So, now when we execute the curl command:

所以,现在当我们执行curl命令时

curl -i http://localhost:8080/spring-rest/redirectWithRedirectPrefix

we’ll immediately get redirected:

我们会立即被重定向。

HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: 
  http://localhost:8080/spring-rest/redirectedUrl?attribute=redirectWithRedirectPrefix

5. Forward With the Prefix forward:

5.用前缀转发:

Let’s now see how to do something slightly different: a forward.

现在让我们看看如何做一些稍有不同的事情:前进。

Before the code, let’s go over a quick, high-level overview of the semantics of forward vs redirect:

在写代码之前,让我们先来看看关于转发与重定向的语义的快速、高级概述

  • redirect will respond with a 302 and the new URL in the Location header; the browser/client will then make another request to the new URL.
  • forward happens entirely on a server side. The Servlet container forwards the same request to the target URL; the URL won’t change in the browser.

Now let’s look at the code:

现在我们来看看代码。

@Controller
@RequestMapping("/")
public class RedirectController {
    
    @GetMapping("/forwardWithForwardPrefix")
    public ModelAndView redirectWithUsingForwardPrefix(ModelMap model) {
        model.addAttribute("attribute", "forwardWithForwardPrefix");
        return new ModelAndView("forward:/redirectedUrl", model);
    }
}

Same as redirect:, the forward: prefix will be resolved by UrlBasedViewResolver and its subclasses. Internally, this will create an InternalResourceView, which does a RequestDispatcher.forward() to the new view.

redirect:相同,forward:前缀将由UrlBasedViewResolver及其子类解决。在内部,这将创建一个InternalResourceView,它对新视图进行RequestDispatcher.forward()

When we execute the command with curl:

当我们用curl执行命令时。

curl -I http://localhost:8080/spring-rest/forwardWithForwardPrefix

we will get HTTP 405 (Method not allowed):

我们将得到HTTP 405(方法不允许)。

HTTP/1.1 405 Method Not Allowed
Server: Apache-Coyote/1.1
Allow: GET
Content-Type: text/html;charset=utf-8

To wrap up, compared to the two requests that we had in the case of the redirect solution, in this case, we only have a single request going out from the browser/client to the server side. The attribute that was previously added by the redirect is, of course, missing as well.

总结一下,与我们在重定向解决方案中的两个请求相比,在这种情况下,我们只有一个请求从浏览器/客户端到服务器端。当然,之前由重定向添加的属性也不见了。

6. Attributes With the RedirectAttributes

6.带有RedirectAttributes的属性

Next, let’s look closer at passing attributes in a redirect, making full use of the framework with RedirectAttributes:

接下来,让我们仔细看看在重定向中传递属性,充分利用RedirectAttributes的框架。

@GetMapping("/redirectWithRedirectAttributes")
public RedirectView redirectWithRedirectAttributes(RedirectAttributes attributes) {
 
    attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectAttributes");
    attributes.addAttribute("attribute", "redirectWithRedirectAttributes");
    return new RedirectView("redirectedUrl");
}

As we saw before, we can inject the attributes object in the method directly, which makes this mechanism very easy to use.

正如我们之前看到的,我们可以直接在方法中注入属性对象,这使得这个机制非常容易使用。

Notice also that we are adding a flash attribute as well. This is an attribute that won’t make it into the URL.

请注意,我们也添加了一个Flash属性。这是一个不会进入URL的属性。

With this kind of attribute, we can later access the flash attribute using @ModelAttribute(“flashAttribute”) only in the method that is the final target of the redirect:

有了这种属性,我们以后可以使用@ModelAttribute(“flashAttribute”) 只在作为重定向最终目标的方法中访问flash属性

@GetMapping("/redirectedUrl")
public ModelAndView redirection(
  ModelMap model, 
  @ModelAttribute("flashAttribute") Object flashAttribute) {
     
     model.addAttribute("redirectionAttribute", flashAttribute);
     return new ModelAndView("redirection", model);
 }

So, to wrap up, if we test the functionality with curl:

所以,总结一下,如果我们用curl来测试功能。

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

we will be redirected to the new location:

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

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

That way, using RedirectAttributes instead of a ModelMap gives us the ability only to share some attributes between the two methods that are involved in the redirect operation.

这样,使用RedirectAttributes而不是ModelMap使我们只能在参与重定向操作的两个方法之间共享一些属性。

7. An Alternative Configuration Without the Prefix

7.没有前缀的替代配置

Let’s now explore an alternative configuration: a redirect without using the prefix.

现在让我们来探索另一种配置:不使用前缀的重定向。

To achieve this, we need to use an org.springframework.web.servlet.view.XmlViewResolver:

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

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

This is instead of org.springframework.web.servlet.view.InternalResourceViewResolver we used in the previous configuration:

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

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

We also need to define a RedirectView bean in the configuration:

我们还需要在配置中定义一个RedirectViewbean。

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

Now we can trigger the redirect by referencing this new bean by id:

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

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

And to test it, we’ll again use the curl command:

而为了测试它,我们将再次使用curl命令

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

And here’s our result:

这就是我们的结果。

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

8. Redirecting an HTTP POST Request

8.重定向一个HTTP POST请求

For use cases like bank payments, we might need to redirect an HTTP POST request. Depending on the HTTP status code returned, POST request can be redirected to an HTTP GET or POST.

对于像银行支付这样的用例,我们可能需要重定向一个HTTP POST请求。根据返回的HTTP状态代码,POST请求可以被重定向为HTTP GET或POST。

As per HTTP 1.1 protocol reference, status codes 301 (Moved Permanently) and 302 (Found) allow the request method to be changed from POST to GET. The specification also defines the corresponding 307 (Temporary Redirect) and 308 (Permanent Redirect) status codes that don’t allow the request method to be changed from POST to GET.

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

Let’s look at the code for redirecting a post request to another post request:

让我们看一下将一个帖子请求重定向到另一个帖子请求的代码。

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

Now we’ll test the redirect of POST using the curl command:

现在我们将使用curl命令测试POST的重定向

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

We get redirected to the destined location:

我们被重定向到指定的地点。

> 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"}

9. Forward With Parameters

9.带参数转发

Now let’s consider a scenario where we would want to send some parameters across to another RequestMapping with a forward prefix.

现在让我们考虑一种情况,我们想把一些参数发送到另一个带有forward前缀的RequestMapping

In that case, we can use an HttpServletRequest to pass in parameters between calls.

在这种情况下,我们可以使用一个HttpServletRequest来在调用之间传递参数。

Here’s a method forwardWithParams that needs to send param1 and param2 to another mapping forwardedWithParams:

这里有一个方法forwardWithParams,需要将param1param2发送到另一个映射forwardedWithParams

@RequestMapping(value="/forwardWithParams", method = RequestMethod.GET)
public ModelAndView forwardWithParams(HttpServletRequest request) {
    request.setAttribute("param1", "one");
    request.setAttribute("param2", "two");
    return new ModelAndView("forward:/forwardedWithParams");
}

In fact, the mapping forwardedWithParams can exist in an entirely new controller and need not be in the same one:

事实上,映射forwardedWithParams可以存在于一个全新的控制器中,不需要在同一个控制器中。

@RequestMapping(value="/forwardWithParams", method = RequestMethod.GET)
@Controller
@RequestMapping("/")
public class RedirectParamController {

    @RequestMapping(value = "/forwardedWithParams", method = RequestMethod.GET)
    public RedirectView forwardedWithParams(
      final RedirectAttributes redirectAttributes, HttpServletRequest request) {
        redirectAttributes.addAttribute("param1", request.getAttribute("param1"));
        redirectAttributes.addAttribute("param2", request.getAttribute("param2"));

        redirectAttributes.addAttribute("attribute", "forwardedWithParams");
        return new RedirectView("redirectedUrl");
    }
}

To illustrate, let’s try this curl command:

为了说明问题,让我们试试这个curl命令。

curl -i http://localhost:8080/spring-rest/forwardWithParams

Here’s the result:

结果是这样的。

HTTP/1.1 302 Found
Date: Fri, 19 Feb 2021 05:37:14 GMT
Content-Language: en-IN
Location: http://localhost:8080/spring-rest/redirectedUrl?param1=one&param2=two&attribute=forwardedWithParams
Content-Length: 0

As we can see, param1 and param2 traveled from the first controller to the second one. Finally, they showed up in the redirect named redirectedUrl that forwardedWithParams points to.

我们可以看到,param1param2从第一个控制器到第二个控制器。最后,它们出现在forwardedWithParams指向的重定向中,名为redirectedUrl

10. Conclusion

10.结论

This article illustrated three different approaches to implementing a redirect in Spring, how to handle/pass attributes when doing these redirects and how to handle redirects of HTTP POST requests.

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