«1. Обзор

В этой статье основное внимание будет уделено реализации перенаправления в Spring и обсуждены причины, лежащие в основе каждой стратегии.

2. Зачем делать редирект?

Давайте сначала рассмотрим причины, по которым вам может понадобиться перенаправление в приложении Spring.

Есть много возможных примеров и причин, конечно. Одним из простых может быть отправка данных формы POST, решение проблемы двойной отправки или просто делегирование потока выполнения другому методу контроллера.

Небольшое замечание: типичный шаблон Post/Redirect/Get не решает должным образом проблемы с двойной отправкой — такие проблемы, как обновление страницы до завершения первоначальной отправки, все равно могут привести к двойной отправке.

3. Перенаправление с помощью RedirectView

Давайте начнем с этого простого подхода – и сразу перейдем к примеру:

За кулисами RedirectView вызовет HttpServletResponse.sendRedirect() – который будет выполнять фактическое перенаправление.

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

Обратите внимание, как мы внедряем атрибуты перенаправления в метод — инфраструктура сделает здесь всю тяжелую работу и позволит нам взаимодействовать с этими атрибутами.

Мы добавляем атрибут атрибута модели, который будет отображаться как параметр HTTP-запроса. Модель должна содержать только объекты — обычно строки или объекты, которые можно преобразовать в строки.

Давайте теперь протестируем наш редирект – с помощью простой команды curl:

Результат будет:

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

4. Редирект с префиксом редирект:

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

Предыдущий подход — использование RedirectView — неоптимален по нескольким причинам.

Во-первых, теперь мы связаны с Spring API, потому что мы используем RedirectView непосредственно в нашем коде.

Во-вторых, теперь нам нужно знать с самого начала, при реализации этой операции контроллера, что результатом всегда будет перенаправление, что может не всегда иметь место.

Лучшим вариантом является использование префикса перенаправления: имя представления перенаправления вводится в контроллер, как и любое другое имя логического представления. Контроллер даже не знает, что происходит перенаправление.

Вот как это выглядит:

Когда имя представления возвращается с префиксом перенаправления: — UrlBasedViewResolver (и все его подклассы) распознает это как особое указание на необходимость перенаправления. . Остальная часть имени представления будет использоваться в качестве URL-адреса перенаправления.

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

Быстрое, но важное замечание: когда мы используем это имя логического представления здесь – redirect:/redirectedUrl – мы делаем перенаправление относительно текущего контекста сервлета.

Мы можем использовать имя, такое как перенаправление: http://localhost:8080/spring-redirect-and-forward/redirectedUrl, если нам нужно перенаправить на абсолютный URL-адрес.

Итак, теперь, когда мы выполним команду curl:

Мы немедленно перенаправимся:

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

5. Вперед с префиксом вперед:

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

Давайте теперь посмотрим, как сделать что-то немного другое — вперед.

Прежде чем приступить к коду, давайте кратко рассмотрим семантику перенаправления и перенаправления:

перенаправление ответит кодом 302 и новым URL-адресом в заголовке Location; затем браузер/клиент сделает еще один запрос на новый URL-адрес, пересылка которого происходит полностью на стороне сервера; контейнер Servlet перенаправляет тот же запрос на целевой URL; URL-адрес не изменится в браузере

    Теперь давайте посмотрим на код:

То же, что и redirect:, префикс forward: будет разрешен с помощью UrlBasedViewResolver и его подклассов. Внутри это создаст InternalResourceView, который выполняет RequestDispatcher.forward() для нового представления.

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

Когда мы выполним команду с помощью curl:

Мы получим HTTP 405 (метод не разрешен):

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

Подводя итог, по сравнению с двумя запросами, которые у нас были в случае решение перенаправления, в этом случае у нас есть только один запрос, исходящий от браузера/клиента на сторону сервера. Атрибут, ранее добавленный перенаправлением, конечно, тоже отсутствует.

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

6. Атрибуты с RedirectAttributes

«Далее — давайте более подробно рассмотрим передачу атрибутов в перенаправлении — полностью используя структуру с RedirectAttribures:

Как мы видели ранее, мы можем напрямую внедрить объект атрибутов в метод — что делает этот механизм очень прост в использовании.

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

Обратите внимание, что мы также добавляем атрибут flash — это атрибут, который не попадет в URL. Чего мы можем добиться с помощью этого типа атрибута, так это того, что позже мы можем получить доступ к атрибуту flash, используя @ModelAttribute(“flashAttribute”) только в методе, который является конечной целью перенаправления:

Итак, в завершение — если мы проверим функциональность с помощью curl:

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

Мы будем перенаправлены в новое местоположение:

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

Таким образом, использование RedirectAttribures вместо ModelMap дает нам возможность только совместно использовать некоторые атрибуты между двумя методами, участвующими в операции перенаправления.

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

7. Альтернативная конфигурация без префикса

Давайте теперь рассмотрим альтернативную конфигурацию – перенаправление без использования префикса.

Для этого нам нужно использовать org.springframework.web.servlet.view.XmlViewResolver:

Вместо org.springframework.web.servlet.view.InternalResourceViewResolver, который мы использовали в предыдущей конфигурации:

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

Нам также нужно определить bean-компонент RedirectView в конфигурации:

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

Теперь мы можем вызвать перенаправление, сославшись на этот новый bean-компонент по идентификатору:

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

И чтобы протестировать его, мы снова воспользуемся командой curl:

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

Результат будет таким:

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

8. Перенаправление HTTP-запроса POST

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

Для случаев использования, таких как банковские платежи, нам может потребоваться перенаправить HTTP-запрос POST. запрос. В зависимости от возвращенного кода состояния HTTP запрос POST может быть перенаправлен на HTTP GET или POST.

Согласно справочнику по протоколу HTTP 1.1, коды состояния 301 (перемещено навсегда) и 302 (найдено) позволяют изменить метод запроса с POST на GET. Спецификация также определяет соответствующие коды состояния 307 (временное перенаправление) и 308 (постоянное перенаправление), которые не позволяют изменить метод запроса с POST на GET.

Теперь давайте посмотрим на код для перенаправления почтового запроса на другой почтовый запрос:

@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");
}

Теперь давайте проверим перенаправление POST с помощью команды curl:

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

Мы получаем перенаправляется в место назначения:

> 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. Пересылка с параметрами

Теперь давайте рассмотрим сценарий, в котором мы хотели бы отправить некоторые параметры другому RequestMapping с префиксом пересылки.

В этом случае мы можем использовать HttpServletRequest для передачи параметров между вызовами.

Вот метод forwardWithParams, который должен отправить param1 и param2 в другое сопоставление 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");
}

На самом деле сопоставление 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");
    }
}

Для иллюстрации давайте попробуем эту команду curl:

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

Вот результат:

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

Как мы видим, param1 и param2 переместились с первого контроллера на второй. Наконец, они появились в перенаправлении с именем redirectedUrl, на которое указывает forwardedWithParams.

10. Заключение

В этой статье показаны три разных подхода к реализации перенаправления в Spring, как обрабатывать/передавать атрибуты при выполнении этих перенаправлений, а также как обрабатывать перенаправления запросов HTTP POST.