Skip to content

Commit 4a95b80

Browse files
committed
Merge branch '6.2.x'
2 parents 113c101 + 46c13ad commit 4a95b80

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

framework-docs/modules/ROOT/pages/integration/rest-clients.adoc

+21
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,27 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
988988

989989

990990

991+
[[rest-http-interface.custom-resolver]]
992+
=== Custom argument resolver
993+
994+
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
995+
This would take over the entire HTTP request and not improve the semantics of the interface.
996+
Instead of adding many method parameters, developers can combine them into a custom type
997+
and configure a dedicated `HttpServiceArgumentResolver` implementation.
998+
999+
In the following HTTP interface, we are using a custom `Search` type as a parameter:
1000+
1001+
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
1002+
1003+
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
1004+
and writes its data in the outgoing HTTP request.
1005+
1006+
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
1007+
1008+
Finally, we can use this argument resolver during the setup and use our HTTP interface.
1009+
1010+
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
1011+
9911012
[[rest-http-interface-return-values]]
9921013
=== Return Values
9931014

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.resthttpinterface.customresolver;
18+
19+
import java.util.List;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.web.client.RestClient;
23+
import org.springframework.web.client.support.RestClientAdapter;
24+
import org.springframework.web.service.annotation.GetExchange;
25+
import org.springframework.web.service.invoker.HttpRequestValues;
26+
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
27+
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
28+
29+
public class CustomHttpServiceArgumentResolver {
30+
31+
// tag::httpinterface[]
32+
interface RepositoryService {
33+
34+
@GetExchange("/repos/search")
35+
List<Repository> searchRepository(Search search);
36+
37+
}
38+
// end::httpinterface[]
39+
40+
class Sample {
41+
42+
void sample() {
43+
// tag::usage[]
44+
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
45+
RestClientAdapter adapter = RestClientAdapter.create(restClient);
46+
HttpServiceProxyFactory factory = HttpServiceProxyFactory
47+
.builderFor(adapter)
48+
.customArgumentResolver(new SearchQueryArgumentResolver())
49+
.build();
50+
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
51+
52+
Search search = Search.create()
53+
.owner("spring-projects")
54+
.language("java")
55+
.query("rest")
56+
.build();
57+
List<Repository> repositories = repositoryService.searchRepository(search);
58+
// end::usage[]
59+
}
60+
61+
}
62+
63+
// tag::argumentresolver[]
64+
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
65+
@Override
66+
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
67+
if (parameter.getParameterType().equals(Search.class)) {
68+
Search search = (Search) argument;
69+
requestValues.addRequestParameter("owner", search.owner());
70+
requestValues.addRequestParameter("language", search.language());
71+
requestValues.addRequestParameter("query", search.query());
72+
return true;
73+
}
74+
return false;
75+
}
76+
}
77+
// end::argumentresolver[]
78+
79+
80+
record Search (String query, String owner, String language) {
81+
82+
static Builder create() {
83+
return new Builder();
84+
}
85+
86+
static class Builder {
87+
88+
Builder query(String query) { return this;}
89+
90+
Builder owner(String owner) { return this;}
91+
92+
Builder language(String language) { return this;}
93+
94+
Search build() {
95+
return new Search(null, null, null);
96+
}
97+
}
98+
99+
}
100+
101+
record Repository(String name) {
102+
103+
}
104+
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.resthttpinterface.customresolver
18+
19+
import org.springframework.core.MethodParameter
20+
import org.springframework.web.client.RestClient
21+
import org.springframework.web.client.support.RestClientAdapter
22+
import org.springframework.web.service.annotation.GetExchange
23+
import org.springframework.web.service.invoker.HttpRequestValues
24+
import org.springframework.web.service.invoker.HttpServiceArgumentResolver
25+
import org.springframework.web.service.invoker.HttpServiceProxyFactory
26+
27+
class CustomHttpServiceArgumentResolver {
28+
29+
// tag::httpinterface[]
30+
interface RepositoryService {
31+
32+
@GetExchange("/repos/search")
33+
fun searchRepository(search: Search): List<Repository>
34+
35+
}
36+
// end::httpinterface[]
37+
38+
class Sample {
39+
fun sample() {
40+
// tag::usage[]
41+
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
42+
val adapter = RestClientAdapter.create(restClient)
43+
val factory = HttpServiceProxyFactory
44+
.builderFor(adapter)
45+
.customArgumentResolver(SearchQueryArgumentResolver())
46+
.build()
47+
val repositoryService = factory.createClient<RepositoryService?>(RepositoryService::class.java)
48+
49+
val search = Search(owner = "spring-projects", language = "java", query = "rest")
50+
val repositories = repositoryService.searchRepository(search)
51+
// end::usage[]
52+
}
53+
}
54+
55+
// tag::argumentresolver[]
56+
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
57+
override fun resolve(
58+
argument: Any?,
59+
parameter: MethodParameter,
60+
requestValues: HttpRequestValues.Builder
61+
): Boolean {
62+
if (parameter.getParameterType() == Search::class.java) {
63+
val search = argument as Search
64+
requestValues.addRequestParameter("owner", search.owner)
65+
.addRequestParameter("language", search.language)
66+
.addRequestParameter("query", search.query)
67+
return true
68+
}
69+
return false
70+
}
71+
}
72+
// end::argumentresolver[]
73+
74+
data class Search(val query: String, val owner: String, val language: String)
75+
76+
data class Repository(val name: String)
77+
}

0 commit comments

Comments
 (0)