이 섹션에서는 URI로 작업하기 위해 Spring Framework에서 사용할 수 있는 다양한 옵션을 설명합니다.
UriComponentsBuilder는 다음 예제와 같이 변수가 있는 URI 템플릿에서 URI를 빌드하는 데 도움이 됩니다.
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") // (1)
.queryParam("q", "{q}") //(2)
.encode() //(3)
.build(); //(4)
URI uri = uriComponents.expand("Westin", "123").toUri(); // (5)
(1) URI 템플릿이 있는 정적 팩토리 메서드입니다.
(2) URI 구성 요소를 추가하거나 교체합니다.
(3) URI 템플릿 및 URI 변수를 인코딩하도록 요청합니다.
(4) UriComponents를 빌드합니다.
(5) 변수를 확장하고 URI를 가져옵니다.
앞의 예제는 다음 예제와 같이 하나의 체인으로 통합되고 buildAndExpand로 단축될 수 있습니다.
URI 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");
다음 예제와 같이 전체 URI 템플릿을 사용하여 더 줄일 수 있습니다.
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
UriComponentsBuilder는 UriBuilder를 구현합니다. UriBuilderFactory를 사용하여 차례로 UriBuilder를 만들 수 있습니다. UriBuilderFactory와 UriBuilder는 함께 기본 URL, 인코딩 기본 설정 및 기타 세부 정보와 같은 공유 구성을 기반으로 URI 템플릿에서 URI를 빌드하는 플러그형 메커니즘을 제공합니다.
UriBuilderFactory를 사용하여 RestTemplate 및 WebClient를 구성하여 URI 준비를 사용자 지정할 수 있습니다. DefaultUriBuilderFactory는 내부적으로 UriComponentsBuilder를 사용하고 공유 구성 옵션을 노출하는 UriBuilderFactory의 기본 구현입니다.
다음 예에서는 RestTemplate을 구성하는 방법을 보여줍니다.
// import 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);
다음 예에서는 WebClient를 구성합니다.
// import 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();
또한 DefaultUriBuilderFactory를 직접 사용할 수도 있습니다. UriComponentsBuilder를 사용하는 것과 유사하지만 정적 팩토리 메서드 대신 다음 예제와 같이 구성 및 기본 설정을 보유하는 실제 인스턴스입니다.
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
UriComponentsBuilder는 두 가지 수준에서 인코딩 옵션을 제공합니다.
UriComponentsBuilder#encode(): 먼저 URI 템플릿을 사전 인코딩한 다음 확장될 때 URI 변수를 엄격하게 인코딩합니다.
UriComponents#encode(): URI 변수가 확장된 후 URI 구성 요소를 인코딩합니다.
두 옵션 모두 ASCII가 아닌 문자와 잘못된 문자를 이스케이프된 옥텟으로 바꿉니다. 그러나 첫 번째 옵션은 또한 URI 변수에 나타나는 예약된 의미로 문자를 바꿉니다.
경로에서는 유효하지만 예약된 의미를 갖는 ";"을 고려하십시오. 첫 번째 옵션은 ";" URI 변수에는 "%3B"가 있지만 URI 템플릿에는 없습니다. 대조적으로, 두 번째 옵션은 경로에서 유효한 문자이기 때문에 ";"을 대체하지 않습니다.
대부분의 경우 첫 번째 옵션은 URI 변수를 불투명한 데이터로 처리하여 완전히 인코딩되기 때문에 예상한 결과를 제공할 가능성이 높으며, 두 번째 옵션은 URI 변수에 의도적으로 예약된 문자가 포함된 경우에 유용합니다. 두 번째 옵션은 URI 변수를 전혀 확장하지 않는 경우에도 유용합니다. 이는 우연히 URI 변수처럼 보이는 모든 항목도 인코딩하기 때문입니다.
다음 예에서는 첫 번째 옵션을 사용합니다.
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
다음 예제와 같이 URI(인코딩을 의미함)로 직접 이동하여 앞의 예제를 줄일 수 있습니다.
URI 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");
WebClient 및 RestTemplate은 UriBuilderFactory 전략을 통해 내부적으로 URI 템플릿을 확장하고 인코딩합니다. 다음 예제와 같이 둘 다 사용자 지정 전략으로 구성할 수 있습니다.
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
DefaultUriBuilderFactory 구현은 내부적으로 UriComponentsBuilder를 사용하여 URI 템플릿을 확장하고 인코딩합니다. Factory로서 아래 인코딩 모드 중 하나를 기반으로 인코딩 접근 방식을 구성할 수 있는 단일 위치를 제공합니다.
TEMPLATE_AND_VALUES: 이전 목록의 첫 번째 옵션에 해당하는 UriComponentsBuilder#encode()를 사용하여 URI 템플릿을 미리 인코딩하고 확장될 때 URI 변수를 엄격하게 인코딩합니다.
VALUES_ONLY: URI 템플릿을 인코딩하지 않고 대신 템플릿으로 확장하기 전에 UriUtils#encodeUriVariables를 통해 URI 변수에 엄격한 인코딩을 적용합니다.
URI_COMPONENT: 이전 목록의 두 번째 옵션에 해당하는 UriComponents#encode()를 사용하여 URI 변수가 확장된 후 URI 구성 요소 값을 인코딩합니다.
NONE: 인코딩이 적용되지 않습니다.
RestTemplate은 역사적인 이유와 이전 버전과의 호환성을 위해 EncodingMode.URI_COMPONENT로 설정됩니다. WebClient는 5.0.x의 EncodingMode.URI_COMPONENT에서 5.1의 EncodingMode.TEMPLATE_AND_VALUES로 변경된 DefaultUriBuilderFactory의 기본값에 의존합니다.
다음 예제와 같이 ServletUriComponentsBuilder를 사용하여 현재 요청과 관련된 URI를 만들 수 있습니다.
HttpServletRequest request = ...
// Re-uses host, scheme, port, path and query string...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
다음 예제와 같이 컨텍스트 경로를 기준으로 URI를 생성할 수 있습니다.
// Re-uses host, port and context path...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
다음 예제와 같이 서블릿과 관련된 URI(예: /main/*)를 만들 수 있습니다.
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
5.1부터 ServletUriComponentsBuilder는 클라이언트에서 시작된 주소를 지정하는 Forwarded 및 X-Forwarded-* 헤더의 정보를 무시합니다. ForwardedHeaderFilter를 사용하여 이러한 헤더를 추출 및 사용하거나 삭제하는 것을 고려하십시오.
Spring MVC는 컨트롤러 메소드에 대한 링크를 준비하는 메커니즘을 제공합니다. 예를 들어 다음 MVC 컨트롤러는 링크 생성을 허용합니다.
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
다음 예제와 같이 이름으로 메서드를 참조하여 링크를 준비할 수 있습니다.
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
앞의 예에서는 경로 변수로 사용하고 URL에 삽입할 실제 메서드 인수 값(이 경우 Long 값: 21)을 제공합니다. 또한 유형 수준 요청 매핑에서 상속된 hotel 변수와 같은 나머지 URI 변수를 채우기 위해 값 42를 제공합니다. 메서드에 더 많은 인수가 있는 경우 URL에 필요하지 않은 인수에 대해 null을 제공할 수 있습니다. 일반적으로 @PathVariable 및 @RequestParam 인수만 URL 구성과 관련이 있습니다.
MvcUriComponentsBuilder를 사용하는 추가 방법이 있습니다. 예를 들어 다음 예제와 같이 이름으로 컨트롤러 메서드를 참조하지 않도록 프록시를 통한 mock 테스트와 유사한 기술을 사용할 수 있습니다(이 예제에서는 MvcUriComponentsBuilder.on의 정적 가져오기를 가정함).
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
컨트롤러 메서드 서명은 fromMethodCall을 사용하여 링크 생성에 사용할 수 있어야 하는 경우 설계가 제한됩니다. 적절한 매개변수 서명이 필요한 것 외에도 반환 유형(즉, 링크 빌더 호출을 위한 런타임 프록시 생성)에 대한 기술적인 제한이 있으므로 반환 type은 final type이 아니어야 합니다. 특히 view 이름에 대한 일반적인 String 반환 type은 여기에서 작동하지 않습니다. 대신 ModelAndView 또는 일반 Object(String 반환 값 포함)를 사용해야 합니다.
이전 예제에서는 MvcUriComponentsBuilder에서 정적 메서드를 사용합니다. 내부적으로 그들은 ServletUriComponentsBuilder에 의존하여 현재 요청의 체계, 호스트, 포트, 컨텍스트 경로 및 서블릿 경로에서 기본 URL을 준비합니다. 이것은 대부분의 경우에 잘 작동합니다. 그러나 때로는 충분하지 않을 수 있습니다. 예를 들어, 요청 컨텍스트 외부에 있거나(예: 링크를 준비하는 일괄 처리 프로세스) 경로 접두사(예: 요청 경로에서 제거되어 다시 작성해야 하는 로케일 접두사)를 삽입해야 할 수 있습니다. 링크에 삽입됨).
이러한 경우, UriComponentsBuilder를 허용하는 정적 fromXxx 오버로드된 메서드를 사용하여 기본 URL을 사용할 수 있습니다. 또는 기본 URL을 사용하여 MvcUriComponentsBuilder의 인스턴스를 만든 다음 인스턴스 기반 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();
5.1부터 MvcUriComponentsBuilder는 클라이언트에서 시작된 주소를 지정하는 Forwarded 및 X-Forwarded-* 헤더의 정보를 무시합니다. ForwardedHeaderFilter를 사용하여 이러한 헤더를 추출 및 사용하거나 삭제하는 것을 고려하십시오.
Thymeleaf, FreeMarker 또는 JSP와 같은 view에서 각 요청 매핑에 대해 암시적 또는 명시적으로 할당된 이름을 참조하여 annotation이 달린 컨트롤러에 대한 링크를 구축할 수 있습니다.
다음 예를 고려하십시오.
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
앞의 컨트롤러가 주어지면 다음과 같이 JSP에서 링크를 준비할 수 있습니다.
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
앞의 예는 Spring 태그 라이브러리(즉, META-INF/spring.tld)에 선언된 mvcUrl 함수에 의존하지만, 자신만의 함수를 정의하거나 다른 템플릿 기술을 위해 유사한 함수를 준비하기 쉽습니다.
작동 방식은 다음과 같습니다. 시작 시 모든 @RequestMapping에는 HandlerMethodMappingNamingStrategy를 통해 기본 이름이 할당되며, 기본 구현은 클래스의 대문자와 메서드 이름을 사용합니다(예: ThingController의 getThing 메서드는 "TC#getThing"이 됨). 이름 충돌이 있는 경우 @RequestMapping(name="..")을 사용하여 명시적 이름을 할당하거나 고유한 HandlerMethodMappingNamingStrategy를 구현할 수 있습니다.