UriComponents
Spring MVC та Spring WebFlux
UriComponentsBuilder
допомагає створювати URI-ідентифікатори з URI-шаблонів зі змінними, як показано в
наведеному нижче прикладі:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel }")
.queryParam("q", "{q}")
.encode()
.build();
URI uri = uriComponents.expand("Westin", "123").toUri();
- Статичний фабричний метод з URI-шаблоном.
- Додаємо або замінюємо URI-компоненти.
- Запит на кодування URI-шаблону та URI-змінних.
- Збираємо
UriComponents
. - Розширюємо змінні та отримуємо
URI
.
val uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam(" q", "{q}")
.encode()
.build()
val uri = uriComponents.expand("Westin", "123").toUri()
- Статичний фабричний метод з URI-шаблоном.
- Додаємо або замінюємо URI-компоненти.
- Запит на кодування URI-шаблону та URI-змінних.
- Збираємо
UriComponents
. - Розширюємо змінні та отримуємо
URI
.
Код із попереднього прикладу можна об'єднати в один ланцюжок і скоротити за допомогою
buildAndExpand
, як показано в наступному прикладі:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri()
Можна скоротити його ще більше шляхом безпосереднього переходу до URI-ідентифікатора (що має на увазі кодування), як показано в наступному прикладі:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
Можна скоротити його навіть ще більше за допомогою повного URI-шаблону, як показано у наступному прикладі:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123")
UriBuilder
Spring MVC та Spring WebFlux
UriComponentsBuilder
реалізує UriBuilder
. Можна створити UriBuilder
за
допомогою UriBuilderFactory
. Водночас UriBuilderFactory
та UriBuilder
надають
механізм, що підключається, для створення URI-ідентифікаторів з URI-шаблонів на основі загальної конфігурації, такої
як базова URL-адреса, параметри кодування та інші деталі.
Можна конфігурувати RestTemplate
та WebClient
за допомогою UriBuilderFactory
, щоб налаштувати підготовку
URI-ідентифікаторів. DefaultUriBuilderFactory
— це реалізація UriBuilderFactory
за
замовчуванням, яка використовує UriComponentsBuilder
на внутрішньому рівні та відкриває загальні
параметри
конфігурації.
У наступному прикладі показано, як налаштувати такий бін:
// імпортуємо org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// імпортуємо org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory
У наступному прикладі конфігурується WebClient
:
// імпортуємо org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
// імпортуємо org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val client = WebClient.builder().uriBuilderFactory(factory).build()
До того ж, можна використовувати DefaultUriBuilderFactory
безпосередньо. Це схоже на
використання
UriComponentsBuilder
, але замість статичних фабричних методів це реальний екземпляр, який
зберігає
конфігурацію та параметри, як показано в наступному прикладі:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)
val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
Кодування URI-ідентифікаторів
Spring MVC та Spring WebFlux
UriComponentsBuilder
відкриває опції кодування на двох рівнях:
UriComponentsBuilder#encode(): Спочатку заздалегідь кодує URI-шаблон, а потім суворо кодує змінні URI-ідентифікаторів при розширенні.
UriComponents#encode(): Кодує URI-компоненти після розширення змінних URI-ідентифікаторів.
Обидві опції замінюють символи, що не належать до стандарту ASCII, та неприпустимі символи на екрановані октети. Однак перший варіант також замінює символи з зарезервованим значенням, які з'являються в змінних URI-ідентифікаторів.
У більшості випадків перший варіант, швидше за все, дасть очікуваний результат, оскільки він враховує змінні URI-ідентифікаторів як непрозорі дані, які мають бути повністю закодовані, у той час як другий варіант корисний, якщо змінні URI-ідентифікаторів свідомо містять зарезервовані символи. Другий варіант також працює, якщо не розширювати змінні URI-ідентифікаторів взагалі, оскільки в цьому випадку кодується все, що випадково буде схоже на URI-змінну.
У наступному прикладі використовується перший варіант:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand ("New York", "foo+bar")
.toUri();
// Результат "/hotel%20list/New%20York?q=foo%2Bbar"
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri ()
// Результат "/hotel%20list/New%20York?q=foo%2Bbar"
Можна скоротити код у попередньому прикладі шляхом безпосереднього переходу до URI-ідентифікатору (що має на увазі кодування), як показано в наступному прикладі:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar")
Можна скоротити його навіть ще більше за допомогою повного URI-шаблону, як показано в наступному прикладі:
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar")
WebClient
та RestTemplate
розширюють та кодують URI-шаблони на внутрішньому
рівні за
допомогою стратегії UriBuilderFactory
. Обидва варіанти можна конфігурувати за допомогою
кастомної
стратегії, як показано в наступному прикладі:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory
factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Налаштовуємо RestTemplate...
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Налаштовуємо WebClient...
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}
// Налаштовуємо RestTemplate()...
val restTemplate = RestTemplate().apply {
uriTemplateHandler = factory
}
// Налаштовуємо WebClient...
val client = WebClient.builder().uriBuilderFactory(factory).build()
Реалізація DefaultUriBuilderFactory
використовує UriComponentsBuilder
на
внутрішньому рівні для розширення та кодування URI-шаблонів. Як фабрика вона надає єдине місце для
конфігурування
підходу до кодування, заснованого на одному з наведених нижче режимів кодування:
TEMPLATE_AND_VALUES
: ВикористовуєUriComponentsBuilder# encode()
, що відповідає першій опції в попередньому списку, для попереднього кодування URI-шаблону та суворого кодування змінних URI-ідентифікаторів при розширенні.VALUES_ONLY
: Не кодує URI-шаблон і, замість цього, застосовує суворе кодування до змінних URI-ідентифікаторів черезUriUtils#encodeUriVariables
перед тим, як розширити їх у шаблон.URI_COMPONENT
: ВикористовуєUriComponents#encode()
, що відповідає другому варіанту в попередньому списку, для кодування значення компонента URI-ідентифікатора після розширення URI-змінних.NONE
: Кодування не застосовується.
RestTemplate
встановлений у EncodingMode.URI_COMPONENT
з історичних причин та для
зворотної сумісності. WebClient
звертається до значення за замовчуванням у DefaultUriBuilderFactory
,
яке було змінено з EncodingMode.URI_COMPONENT
в 5.0.x на
EncodingMode.TEMPLATE_AND_VALUES
у 5.1.
Відносні запити сервлетів
Ти можеш використовувати ServletUriComponentsBuilder
для створення URI-ідентифікаторів щодо поточного запиту, як показано в наступному прикладі:
HttpServletRequest request = ...
// Повторно використовує схему, хост, порт, шлях і рядок запиту...
URI uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123");
val request: HttpServletRequest = ...
// Повторно використовує схему, хост, порт, шлях і рядок запиту...
val uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build ("123")
Можна створювати URI-ідентифікатори щодо контекстного шляху, як показано в наступному прикладі:
HttpServletRequest request = ...
// Повторне використання схеми, хоста, порту та контекстного шляху...
URI uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri();
val request: HttpServletRequest = ...
// Повторне використання схеми, хоста, порту та контекстного шляху...
val uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri()
Можна створювати URI-ідентифікатори щодо сервлета (наприклад, /main/*
), як показано в наступному
прикладі:
HttpServletRequest request = ...
// Повторно використовує схему, хост, порт, контекстний шлях і префікс відображення сервлетів...
URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri();
val request: HttpServletRequest = ...
// Повторно використовує схему, хост, порт, контекстний шлях і префікс відображення сервлетів...
val uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri()
ServletUriComponentsBuilder
ігнорує інформацію із заголовків Forwarded
та X-Forwarded-
*
, які вказують адресу з боку клієнта. Розглянь можливість використання
ForwardedHeaderFilter
для отримання та використання або відкидання таких заголовків.Посилання на контролери
Spring MVC містить механізм для підготовки посилань на методи контролера. Наприклад, наступний контролер MVC дозволяє створювати посилання:
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping(" /bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
@Controller
@RequestMapping("/hotels/{hotel}")
class BookingController {
@GetMapping("/bookings/{booking}")
fun getBooking(@PathVariable booking: Long): ModelAndView {
// ...
}
}
Можна підготувати посилання, якщо звернутися до методу на ім'я, як показано в наступному прикладі:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder
.fromMethodName (BookingController::class.java, "getBooking", 21).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
У попередньому прикладі ми вказуємо фактичні значення аргументів методу (у даному випадку довге
значення:
21
)
для використання як змінного шляху та вставки до URL-адреси. До того ж, ми вказуємо значення
42
для
заповнення будь-яких змінних URI-ідентифікаторів, таких як змінна hotel
, успадкована
від
відображення запиту на рівні типів. Якби метод мав більше аргументів, ми могли б поставити null для
аргументів,
які не потрібні для URL-адреси. Загалом, лише аргументи з анотаціями @PathVariable
та
@RequestParam
мають значення для побудови URL-адреси.
Існують додаткові способи використання MvcUriComponentsBuilder
. Наприклад, щоб уникнути
звернення
до методу контролера на ім'я, як показано в наступному прикладі (приклад передбачає статичний імпорт
MvcUriComponentsBuilder.on
),
можна використовувати техніку, схожу на тестування з використанням фіктивних об'єктів через проксі:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
val uriComponents = MvcUriComponentsBuilder
.fromMethodCall (on(BookingController::class.java).getBooking(21)).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
fromMethodCall
. Крім необхідності в правильній сигнатурі параметрів, існує технічне
обмеження
на тип, що повертається (а саме створення проксі під час виконання для викликів засобу формування
посилань),
тому тип, що повертається не повинен бути
final
. Зокрема, звичайний тип, що повертається
String
для імен подання тут не працює. Натомість слід використовувати
ModelAndView
або навіть простий Object
(з повертаним значенням
String
).
У попередніх прикладах використовуються статичні методи у MvcUriComponentsBuilder
. На
внутрішньому
рівні вони звертаються до ServletUriComponentsBuilder
для підготовки базової URL-адреси
зі
схеми,
хоста, порту, шляху контексту та шляху сервлету поточного запиту. Це добре працює в більшості
випадків.
Однак іноді
цього може виявитися недостатньо. Наприклад, ти можеш перебувати поза контекстом запиту (наприклад,
при
пакетному
процесі, який готує посилання) або, можливо, тобі потрібно вставити префікс шляху (наприклад,
префікс
регіональних
налаштувань, який був видалений зі шляху запиту і повинен бути знову вставлений у посилання).
Для таких
випадків можна використовувати статичні перевантажені методи fromXxx
, які приймають
UriComponentsBuilder
для використання базової URL-адреси. До того ж, можна створити екземпляр
MvcUriComponentsBuilder
з
базовою URL-адресою, а потім використовувати методи withXxx
, засновані на екземплярі.
Наприклад, у
наступному лістингу використовується withMethodCall
:
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en")
val builder = MvcUriComponentsBuilder.relativeTo(base)
builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)
val uri = uriComponents.encode().toUri()
MvcUriComponentsBuilder
ігнорує
інформацію із заголовків Forwarded
та X-Forwarded-*
, в яких вказується
адреса
з боку
клієнта. Розглянь можливість використання ForwardedHeaderFilter для вилучення і використання або
відкидання таких
заголовків.
Посилання в поданні
У таких поданнях як Thymeleaf, FreeMarker або JSP, можна формувати посилання на анотовані контролери, посилаючись на неявно або явно надане ім'я для кожного відображення запиту.
Розглянемо наступний приклад:
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
@RequestMapping("/people/{id}/addresses")
class PersonAddressController {
@RequestMapping("/{country}")
fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... }
}
Враховуючи попередній контролер, можна підготувати посилання з JSP у наступному вигляді:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
У попередньому прикладі ми покладаємося на функцію mvcUrl
, оголошену в бібліотеці
тегів
Spring
(тобто META-INF/spring.tld), але визначити власну функцію або підготувати аналогічну для інших
технологій
шаблонизації також досить просто.
Ось як це працює. Під час запуску кожної анотації @RequestMapping
привласнюється стандартне ім'я через стартегію HandlerMethodMappingNamingStrategy
,
реалізація якої за
замовчуванням використовує великі літери класу та ім'я методу (наприклад, метод get
ThingController
стає "TC#getThing"). Якщо імена не збігаються, можна
використовувати @RequestMapping(name="..")
для призначення явного імені або реалізувати власну стратегію іменування HandlerMethodMappingNamingStrategy
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ