«1. Обзор

Spring 5 представил новый PathPatternParser для анализа шаблонов шаблонов URI. Это альтернатива ранее используемому AntPathMatcher.

AntPathMatcher был реализацией сопоставления путей с образцом в стиле Ant. PathPatternParser разбивает путь на связанный список PathElements. Эта цепочка PathElements используется классом PathPattern для быстрого сопоставления шаблонов.

В PathPatternParser также была введена поддержка нового синтаксиса переменных URI.

В этой статье мы рассмотрим новые/обновленные средства сопоставления шаблонов URL, представленные в Spring 5.0 WebFlux, а также те, которые появились в более ранних версиях Spring.

2. Новые средства сопоставления шаблонов URL в Spring 5.0

В версии Spring 5.0 добавлен очень простой в использовании синтаксис переменной URI: {*foo} для захвата любого количества сегментов пути в конце шаблона.

2.1. Синтаксис переменной URI {*foo} с использованием метода обработчика

Давайте рассмотрим пример шаблона переменной URI {*foo} еще один пример использования @GetMapping и метода обработчика. Все, что мы указываем в пути после «/spring5», будет сохранено в переменной пути «id»:

@GetMapping("/spring5/{*id}")
public String URIVariableHandler(@PathVariable String id) {
    return id;
}
@Test
public void whenMultipleURIVariablePattern_thenGotPathVariable() {
        
    client.get()
      .uri("/spring5/baeldung/tutorial")
      .exchange()
      .expectStatus()
      .is2xxSuccessful()
      .expectBody()
      .equals("/baeldung/tutorial");

    client.get()
      .uri("/spring5/baeldung")
      .exchange()
      .expectStatus()
      .is2xxSuccessful()
      .expectBody()
      .equals("/baeldung");
}

2.2. Синтаксис переменной URI {*foo} с использованием RouterFunction

private RouterFunction<ServerResponse> routingFunction() {
    return route(GET("/test/{*id}"), 
      serverRequest -> ok().body(fromValue(serverRequest.pathVariable("id"))));
}

Давайте посмотрим на пример нового шаблона пути переменной URI с использованием RouterFunction:

@Test
public void whenMultipleURIVariablePattern_thenGotPathVariable() 
  throws Exception {
 
    client.get()
      .uri("/test/ab/cd")
      .exchange()
      .expectStatus()
      .isOk()
      .expectBody(String.class)
      .isEqualTo("/ab/cd");
}

В этом случае любой путь, который мы напишем после «/test», будет захвачен. в переменной пути «id». Таким образом, тестовый пример для этого может быть:

2.3. Использование синтаксиса переменной URI {*foo} для доступа к ресурсам

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

Допустим, наш шаблон таков: «/files/{*filepaths}». В этом случае, если путь — /files/hello.txt, значение переменной пути «filepaths» будет «/hello.txt», тогда как, если путь — /files/test/test.txt, значение «filepaths» = «/test/test.txt».

private RouterFunction<ServerResponse> routingFunction() { 
    return RouterFunctions.resources(
      "/files/{*filepaths}", 
      new ClassPathResource("files/"))); 
}

Наша функция маршрутизации для доступа к файловым ресурсам в каталоге /files/:

@Test 
public void whenMultipleURIVariablePattern_thenGotPathVariable() 
  throws Exception { 
      client.get() 
        .uri("/files/test/test.txt") 
        .exchange() 
        .expectStatus() 
        .isOk() 
        .expectBody(String.class) 
        .isEqualTo("test");
 
      client.get() 
        .uri("/files/hello.txt") 
        .exchange() 
        .expectStatus() 
        .isOk() 
        .expectBody(String.class) 
        .isEqualTo("hello"); 
}

Предположим, что наши текстовые файлы hello.txt и test.txt содержат «hello» и «test» соответственно. Это можно продемонстрировать с помощью тестового примера JUnit:

3. Существующие шаблоны URL из предыдущих версий

Давайте теперь взглянем на все другие средства сопоставления шаблонов URL, которые поддерживались более ранними версиями Spring. Все эти шаблоны работают как с методами RouterFunction, так и с методами Handler с @GetMapping.

3.1. -? Соответствует ровно одному символу

Если мы укажем шаблон пути как: «/t?st», это будет соответствовать таким путям, как: «/test» и «/tast», но не « /tst” и “/teest”.

private RouterFunction<ServerResponse> routingFunction() { 
    return route(GET("/t?st"), 
      serverRequest -> ok().body(fromValue("Path /t?st is accessed"))); 
}

@Test
public void whenGetPathWithSingleCharWildcard_thenGotPathPattern()   
  throws Exception {
 
      client.get()
        .uri("/test")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("Path /t?st is accessed");
}

Пример кода, использующего RouterFunction и его тестовый сценарий JUnit:

3.2. «*» соответствует 0 или более символам в сегменте пути

private RouterFunction<ServerResponse> routingFunction() { 
    returnroute(
      GET("/baeldung/*Id"), 
      serverRequest -> ok().body(fromValue("/baeldung/*Id path was accessed"))); }

@Test
public void whenGetMultipleCharWildcard_thenGotPathPattern() 
  throws Exception {
      client.get()
        .uri("/baeldung/tutorialId")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("/baeldung/*Id path was accessed");
}

Если мы укажем шаблон пути как: «/baeldung/*Id», это будет соответствовать шаблонам пути, например: «/baeldung/Id», «/baeldung/tutorialId», «/baeldung/articleId» и т. д.:

3.3. ‘**’ Соответствует 0 или более сегментам пути до конца пути

private RouterFunction<ServerResponse> routingFunction() { 
    return RouterFunctions.resources(
      "/resources/**", 
      new ClassPathResource("resources/"))); 
}

@Test
public void whenAccess_thenGot() throws Exception {
    client.get()
      .uri("/resources/test/test.txt")
      .exchange()
      .expectStatus()
      .isOk()
      .expectBody(String.class)
      .isEqualTo("content of file test.txt");
}

В этом случае сопоставление с шаблоном не ограничивается одним сегментом пути. Если мы укажем шаблон как «/resources/**», он будет соответствовать всем путям до любого количества сегментов пути после «/resources/»:

3.4. ‘{baeldung:[a-z]+}’ Регулярное выражение в переменной пути

private RouterFunction<ServerResponse> routingFunction() { 
    return route(GET("/{baeldung:[a-z]+}"), 
      serverRequest ->  ok()
        .body(fromValue("/{baeldung:[a-z]+} was accessed and "
        + "baeldung=" + serverRequest.pathVariable("baeldung")))); 
}

@Test
public void whenGetRegexInPathVarible_thenGotPathVariable() 
  throws Exception {
 
      client.get()
        .uri("/abcd")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("/{baeldung:[a-z]+} was accessed and "
          + "baeldung=abcd");
}

Мы также можем указать регулярное выражение для значения переменной пути. Итак, если наш шаблон похож на «/{baeldung:[a-z]+}», значение переменной пути «baeldung» будет любым сегментом пути, который соответствует регулярному выражению Gives:

3.5. ‘/{var1}_{var2}’ Несколько переменных пути в одном сегменте пути

private RouterFunction<ServerResponse> routingFunction() { 
 
    return route(
      GET("/{var1}_{var2}"),
      serverRequest -> ok()
        .body(fromValue( serverRequest.pathVariable("var1") + " , " 
        + serverRequest.pathVariable("var2"))));
 }

@Test
public void whenGetMultiplePathVaribleInSameSegment_thenGotPathVariables() 
  throws Exception {
      client.get()
        .uri("/baeldung_tutorial")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("baeldung , tutorial");
}

Spring 5 гарантирует, что несколько переменных пути будут разрешены в одном сегменте пути, только если они разделены разделителем. Только тогда Spring сможет различать две разные переменные пути:

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

В этой статье мы рассмотрели новые сопоставители URL в Spring 5, а также те, которые доступны в более старых версиях. весны.