URI リンク

このセクションでは、Spring Framework で URI を操作するために使用できるさまざまなオプションについて説明します。

UriComponents

Spring MVC および Spring WebFlux

UriComponentsBuilder は、次の例に示すように、変数を持つ URI テンプレートから URI を作成できます。

  • Java

  • Kotlin

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)
1URI テンプレートを使用した静的ファクトリメソッド。
2URI コンポーネントを追加または置換します。
3URI テンプレートと URI 変数をエンコードするようリクエストします。
4UriComponents をビルドします。
5 変数を展開し、URI を取得します。
val uriComponents = UriComponentsBuilder
		.fromUriString("https://example.com/hotels/{hotel}") (1)
		.queryParam("q", "{q}") (2)
		.encode() (3)
		.build() (4)

val uri = uriComponents.expand("Westin", "123").toUri() (5)
1URI テンプレートを使用した静的ファクトリメソッド。
2URI コンポーネントを追加または置換します。
3URI テンプレートと URI 変数をエンコードするようリクエストします。
4UriComponents をビルドします。
5 変数を展開し、URI を取得します。

前述の例は、次の例に示すように、1 つのチェーンに統合し、buildAndExpand で短縮できます。

  • Java

  • Kotlin

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 に直接移動することで(エンコードを暗示する)、さらに短くすることができます。

  • Java

  • Kotlin

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 テンプレートを使用してさらに短縮できます。

  • Java

  • Kotlin

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 を実装しています。UriBuilderFactory を使用して、UriBuilder を作成できます。UriBuilderFactory と UriBuilder は、ベース URL、エンコード設定、その他の詳細などの共有構成に基づいて、URI テンプレートから URI を構築するプラグ可能なメカニズムを提供します。

RestTemplate および WebClient を UriBuilderFactory で構成して、URI の準備をカスタマイズできます。DefaultUriBuilderFactory は、UriComponentsBuilder を内部で使用し、共有構成オプションを公開する UriBuilderFactory のデフォルト実装です。

次の例は、RestTemplate を構成する方法を示しています。

  • Java

  • Kotlin

// 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);
// import 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 を構成します。

  • Java

  • Kotlin

// 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();
// import 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 の使用に似ていますが、次の例に示すように、静的ファクトリメソッドの代わりに、構成と設定を保持する実際のインスタンスです。

  • Java

  • Kotlin

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 は 2 つの URI パーサー型をサポートしています。

  1. RFC パーサー — このパーサー型は、URI 文字列が RFC 3986 構文に準拠していることを想定しており、構文からの逸脱は不正として扱います。

  2. WhatWG パーサー — このパーサーは、WhatWG URL 生活水準 (英語) URL 解析アルゴリズム [GitHub] (英語) に基づいています。これは、予期しない入力のさまざまなケースを寛容に処理します。ブラウザーは、ユーザーが入力した URL を寛容に処理するためにこれを実装します。詳細については、URL Living Standard および URL 解析テストケース [GitHub] (英語) を参照してください。

デフォルトでは、RestClientWebClientRestTemplate は RFC パーサー型を使用し、アプリケーションが RFC 構文に準拠した URL テンプレートを提供することを期待します。これを変更するには、いずれかのクライアントで UriBuilderFactory をカスタマイズできます。

アプリケーションとフレームワークは、さらに、ユーザーが提供する URL を解析して、スキーム、ホスト、ポート、パス、クエリなどの URI コンポーネントをインスペクションし、検証するために、独自のニーズに合わせて UriComponentsBuilder に依存する場合があります。このようなコンポーネントは、入力 URL へのリダイレクトの場合やブラウザーへのレスポンスに含まれている場合に、URL をより寛大に処理し、ブラウザーが URI を解析する方法に合わせるために、WhatWG パーサー型を使用することを決定できます。

URI エンコーディング

Spring MVC および Spring WebFlux

UriComponentsBuilder は、2 つのレベルでエンコードオプションを公開します。

どちらのオプションも、非 ASCII 文字と不正な文字をエスケープされたオクテットに置き換えます。ただし、最初のオプションは、URI 変数に表示される予約された意味で文字を置き換えます。

";" を検討してください。これはパスでは有効ですが、意味は予約されています。最初のオプションは ";" を置き換えます。URI 変数には "%3B" が含まれますが、URI テンプレートには含まれません。対照的に、2 番目のオプションはパス内の正当な文字であるため、";" を置き換えることはありません。

ほとんどの場合、最初のオプションは URI 変数を完全にエンコードされる不透明(OPAQUE)データとして扱うため、期待どおりの結果が得られる可能性があります。2 番目のオプションは、URI 変数に意図的に予約文字が含まれている場合に役立ちます。2 番目のオプションは、URI 変数をまったく展開しない場合にも役立ちます。これは、偶然に URI 変数のように見えるものもエンコードするためです。

次の例では、最初のオプションを使用しています。

  • Java

  • Kotlin

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"
val 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 に直接移動することで、前述の例を短縮できます(エンコードを意味します)。

  • Java

  • Kotlin

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 テンプレートを使用してさらに短縮できます。

  • Java

  • Kotlin

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 は、UriBuilderFactory 戦略を通じて内部で URI テンプレートを拡張およびエンコードします。次の例に示すように、どちらもカスタム戦略で構成できます。

  • Java

  • Kotlin

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();
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
	encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}

// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
	uriTemplateHandler = factory
}

// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()

DefaultUriBuilderFactory 実装は、UriComponentsBuilder を内部的に使用して URI テンプレートを展開およびエンコードします。ファクトリとして、以下のエンコードモードのいずれかに基づいて、エンコードへのアプローチを構成する単一の場所を提供します。

  • TEMPLATE_AND_VALUES: 前のリストの最初のオプションに対応する UriComponentsBuilder#encode() を使用して、URI テンプレートを事前エンコードし、展開時に URI 変数を厳密にエンコードします。

  • VALUES_ONLY: URI テンプレートをエンコードせず、代わりに、UriUtils#encodeUriVariables を使用して URI 変数をテンプレートに展開する前に URI 変数に厳密なエンコードを適用します。

  • URI_COMPONENT: 前のリストの 2 番目のオプションに対応する 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 を作成できます。

  • Java

  • Kotlin

HttpServletRequest request = ...

// Re-uses scheme, host, port, path, and query string...

URI uri = ServletUriComponentsBuilder.fromRequest(request)
		.replaceQueryParam("accountId", "{id}")
		.build("123");
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, path, and query string...

val uri = ServletUriComponentsBuilder.fromRequest(request)
		.replaceQueryParam("accountId", "{id}")
		.build("123")

次の例に示すように、コンテキストパスに相対的な URI を作成できます。

  • Java

  • Kotlin

HttpServletRequest request = ...

// Re-uses scheme, host, port, and context path...

URI uri = ServletUriComponentsBuilder.fromContextPath(request)
		.path("/accounts")
		.build()
		.toUri();
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, and context path...

val uri = ServletUriComponentsBuilder.fromContextPath(request)
		.path("/accounts")
		.build()
		.toUri()

次の例に示すように、サーブレットに関連する URI(/main/* など)を作成できます。

  • Java

  • Kotlin

HttpServletRequest request = ...

// Re-uses scheme, host, port, context path, and Servlet mapping prefix...

URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
		.path("/accounts")
		.build()
		.toUri();
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, context path, and Servlet mapping prefix...

val uri = ServletUriComponentsBuilder.fromServletMapping(request)
		.path("/accounts")
		.build()
		.toUri()
5.1 の時点で、ServletUriComponentsBuilder は Forwarded および X-Forwarded-* ヘッダーからの情報を無視します。これらのヘッダーは、クライアントが発信したアドレスを指定します。ForwardedHeaderFilter を使用して、このようなヘッダーを抽出して使用または破棄することを検討してください。

Spring MVC は、コントローラーメソッドへのリンクを準備するメカニズムを提供します。例: 次の MVC コントローラーはリンクの作成を許可します。

  • Java

  • Kotlin

@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 {
		// ...
	}
}

次の例に示すように、名前でメソッドを参照することでリンクを準備できます。

  • Java

  • Kotlin

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 を提供して、型レベルのリクエストマッピングから継承された hotel 変数など、残りの URI 変数を埋めます。メソッドにさらに引数がある場合、URL に不要な引数に null を指定できます。一般に、URL の構築に関連するのは @PathVariable および @RequestParam 引数のみです。

MvcUriComponentsBuilder を使用する追加の方法があります。例: 次の例が示すように、名前によるコントローラーメソッドの参照を回避するために、プロキシを介したモックテストに似た手法を使用できます(例では MvcUriComponentsBuilder.on の静的インポートを想定しています)。

  • Java

  • Kotlin

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 の静的メソッドを使用しています。内部的には、現在のリクエストのスキーム、ホスト、ポート、コンテキストパス、サーブレットパスからベース URL を準備するために ServletUriComponentsBuilder に依存しています。これはほとんどの場合にうまく機能します。ただし、場合によっては不十分な場合があります。例: リクエストのコンテキスト外(リンクを準備するバッチプロセスなど)であるか、パスプレフィックスを挿入する必要がある場合(リクエストパスから削除され、再挿入が必要なロケールプレフィックスなど)リンクに)。

そのような場合、ベース URL を使用するために UriComponentsBuilder を受け入れる静的な fromXxx オーバーロードメソッドを使用できます。または、ベース URL を使用して MvcUriComponentsBuilder のインスタンスを作成し、インスタンスベースの withXxx メソッドを使用できます。例: 次のリストでは withMethodCall を使用しています。

  • Java

  • Kotlin

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()
5.1 の時点で、MvcUriComponentsBuilder は Forwarded および X-Forwarded-* ヘッダーからの情報を無視します。これらのヘッダーは、クライアントが発信したアドレスを指定します。ForwardedHeaderFilter を使用して、このようなヘッダーを抽出して使用または破棄することを検討してください。

Thymeleaf、FreeMarker、JSP などのビューでは、各リクエストマッピングに暗黙的または明示的に割り当てられた名前を参照することにより、アノテーション付きコントローラーへのリンクを構築できます。

次の例を考えてみましょう。

  • Java

  • Kotlin

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

上記の例は、Spring タグライブラリ(つまり、META-INF/spring.tld)で宣言された mvcUrl 関数に依存していますが、独自の関数を定義したり、他のテンプレートテクノロジ用に同様の関数を準備したりするのは簡単です。

これがどのように機能するかを次に示します。起動時に、すべての @RequestMapping には HandlerMethodMappingNamingStrategy を介してデフォルト名が割り当てられます。そのデフォルト実装では、クラスの大文字とメソッド名が使用されます(たとえば、ThingController の getThing メソッドは "TC#getThing" になります)。名前の衝突がある場合は、@RequestMapping(name="..") を使用して明示的な名前を割り当てるか、独自の HandlerMethodMappingNamingStrategy を実装できます。