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)
1 | URI テンプレートを使用した静的ファクトリメソッド。 |
2 | URI コンポーネントを追加または置換します。 |
3 | URI テンプレートと URI 変数をエンコードするようリクエストします。 |
4 | UriComponents をビルドします。 |
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)
1 | URI テンプレートを使用した静的ファクトリメソッド。 |
2 | URI コンポーネントを追加または置換します。 |
3 | URI テンプレートと URI 変数をエンコードするようリクエストします。 |
4 | UriComponents をビルドします。 |
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 パーサー型をサポートしています。
RFC パーサー — このパーサー型は、URI 文字列が RFC 3986 構文に準拠していることを想定しており、構文からの逸脱は不正として扱います。
WhatWG パーサー — このパーサーは、WhatWG URL 生活水準 (英語) の URL 解析アルゴリズム [GitHub] (英語) に基づいています。これは、予期しない入力のさまざまなケースを寛容に処理します。ブラウザーは、ユーザーが入力した URL を寛容に処理するためにこれを実装します。詳細については、URL Living Standard および URL 解析テストケース [GitHub] (英語) を参照してください。
デフォルトでは、RestClient
、WebClient
、RestTemplate
は RFC パーサー型を使用し、アプリケーションが RFC 構文に準拠した URL テンプレートを提供することを期待します。これを変更するには、いずれかのクライアントで UriBuilderFactory
をカスタマイズできます。
アプリケーションとフレームワークは、さらに、ユーザーが提供する URL を解析して、スキーム、ホスト、ポート、パス、クエリなどの URI コンポーネントをインスペクションし、検証するために、独自のニーズに合わせて UriComponentsBuilder
に依存する場合があります。このようなコンポーネントは、入力 URL へのリダイレクトの場合やブラウザーへのレスポンスに含まれている場合に、URL をより寛大に処理し、ブラウザーが URI を解析する方法に合わせるために、WhatWG パーサー型を使用することを決定できます。
URI エンコーディング
Spring MVC および Spring WebFlux
UriComponentsBuilder
は、2 つのレベルでエンコードオプションを公開します。
UriComponentsBuilder#encode() (Javadoc) : 最初に URI テンプレートを事前にエンコードし、次に展開時に URI 変数を厳密にエンコードします。
UriComponents#encode() (Javadoc) : URI 変数が展開された後、 URI コンポーネントをエンコードします。
どちらのオプションも、非 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
を実装できます。