이 섹션에서는 URI 작업을 위해 Spring Framework에서 사용할 수 있는 다양한 옵션을 설명합니다.
스프링 MVC와 스프링 WebFlux 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");
스프링 MVC와 스프링 WebFlux
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");
스프링 MVC와 스프링 WebFlux
UriComponentsBuilder
는 두 가지 수준에서 인코딩 옵션을 노출합니다.
UriComponentsBuilder#encode()
: 먼저 URI 템플릿을 사전 인코딩한 다음 확장 시 URI 변수를 엄격하게 인코딩합니다.
UriComponents#encode()
: URI 변수가 확장된 후 URI 구성 요소를 인코딩합니다.
두 옵션 모두 ASCII가 아닌 문자와 잘못된 문자를 이스케이프된 옥텟으로 바꿉니다. 그러나 첫 번째 옵션도 URI 변수에 나타나는 예약된 의미로 문자를 바꿉니다.
[Tip]
경로에서는 유효하지만 의미가 보존되어 있는 ";"을 고려하세요. 첫 번째 옵션은 ";"을 대체합니다. 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 템플릿을 확장하고 인코딩합니다. 팩토리로서 아래 인코딩 모드 중 하나를 기반으로 인코딩 접근 방식을 구성할 수 있는 단일 위치를 제공합니다.
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 scheme, host, port, path, and query string...
URI uri = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}")
.build("123");
다음 예제와 같이 컨텍스트 경로를 기준으로 URI를 만들 수 있습니다.
HttpServletRequest request = ...
// Re-uses scheme, host, port, and context path...
URI uri = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts")
.build()
.toUri();
다음 예제와 같이 서블릿에 상대적인 URI(예: /main/*
)를 생성할 수 있습니다.
HttpServletRequest request = ...
// Re-uses scheme, host, port, context path, and Servlet mapping prefix...
URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts")
.build()
.toUri();
[Note]
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에 삽입될 실제 메서드 인수 값(이 경우 긴 값: 21
)을 제공합니다. 또한 유형 수준 요청 매핑에서 상속된 hotel
변수와 같은 나머지 URI 변수를 채우기 위해 값 42
를 제공합니다. 메소드에 더 많은 인수가 있는 경우 URL에 필요하지 않은 인수에 대해 null을 제공할 수 있습니다. 일반적으로 @PathVariable
및 @RequestParam
인수만 URL 구성에 관련됩니다.
MvcUriComponentsBuilder
를 사용하는 추가 방법이 있습니다. 예를 들어, 다음 예제에서 볼 수 있듯이 컨트롤러 메서드를 이름으로 참조하지 않도록 프록시를 통한 모의 테스트(mock testing)와 유사한 기술을 사용할 수 있습니다(예에서는 MvcUriComponentsBuilder.on
의 static import를 가정합니다).
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
[Note]
컨트롤러 메서드 서명은fromMethodCall
을 통한 링크 생성에 사용할 수 있다고 가정할 때 디자인이 제한됩니다. 적절한 매개변수 서명이 필요한 것 외에도 반환 유형(즉, 링크 빌더 호출을 위한 런타임 프록시 생성)에는 기술적인 제한이 있으므로 반환 유형은final
이 아니어야 합니다. 특히, 뷰 이름에 대한 일반적인String
반환 유형은 여기서 작동하지 않습니다. 대신ModelAndView
또는 일반Object
(String
반환 값 포함)를 사용해야 합니다.
이전 예제에서는 MvcUriComponentsBuilder
의 정적 메서드를 사용합니다. 내부적으로는 ServletUriComponentsBuilder
를 사용하여 현재 요청의 구성표, 호스트, 포트, 컨텍스트 경로 및 서블릿 경로에서 기본 URL을 준비합니다. 이는 대부분의 경우에 잘 작동합니다. 그러나 때로는 충분하지 않을 수도 있습니다. 예를 들어 요청 컨텍스트 외부에 있거나(예: 링크를 준비하는 일괄 프로세스) 경로 접두사(예: 요청 경로에서 제거되어 다시 수정해야 하는 로케일 접두사)를 삽입해야 할 수 있습니다. 링크에 삽입했습니다).
이러한 경우 기본 URL을 사용하기 위해 UriComponentsBuilder
를 허용하는 정적 fromXxx
오버로드된 메서드를 사용할 수 있습니다. 또는 기본 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와 같은 용도로 각 요청 매핑에 대해 해당하거나 고유하게 이름을 참조하여 추출된 컨트롤러에 대한 링크를 구축할 수 있습니다. 다음 예를 고려하시기 바랍니다.
@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
를 구현할 수 있습니다.