18
18
19
19
import java .lang .reflect .Method ;
20
20
import java .time .Duration ;
21
- import java .util .HashMap ;
21
+ import java .util .ArrayList ;
22
22
import java .util .List ;
23
23
import java .util .Map ;
24
+ import java .util .function .Function ;
25
+ import java .util .stream .Collectors ;
24
26
25
27
import org .aopalliance .intercept .MethodInterceptor ;
26
28
import org .aopalliance .intercept .MethodInvocation ;
29
31
import org .springframework .core .MethodIntrospector ;
30
32
import org .springframework .core .ReactiveAdapterRegistry ;
31
33
import org .springframework .core .annotation .AnnotatedElementUtils ;
34
+ import org .springframework .core .convert .ConversionService ;
35
+ import org .springframework .format .support .DefaultFormattingConversionService ;
36
+ import org .springframework .lang .Nullable ;
37
+ import org .springframework .util .Assert ;
32
38
import org .springframework .web .service .annotation .HttpExchange ;
33
39
34
40
35
41
/**
36
- * Factory to create a proxy for an HTTP service with {@link HttpExchange} methods.
42
+ * Factory for creating a client proxy given an HTTP service interface with
43
+ * {@link HttpExchange @HttpExchange} methods.
37
44
*
38
45
* @author Rossen Stoyanchev
39
46
* @since 6.0
40
47
*/
41
- public class HttpServiceProxyFactory {
42
-
43
- private final List <HttpServiceArgumentResolver > argumentResolvers ;
48
+ public final class HttpServiceProxyFactory {
44
49
45
50
private final HttpClientAdapter clientAdapter ;
46
51
52
+ private final List <HttpServiceArgumentResolver > argumentResolvers ;
53
+
47
54
private final ReactiveAdapterRegistry reactiveAdapterRegistry ;
48
55
49
56
private final Duration blockTimeout ;
50
57
51
58
52
- public HttpServiceProxyFactory (
53
- List <HttpServiceArgumentResolver > argumentResolvers , HttpClientAdapter clientAdapter ,
59
+ private HttpServiceProxyFactory (
60
+ HttpClientAdapter clientAdapter , List <HttpServiceArgumentResolver > argumentResolvers ,
54
61
ReactiveAdapterRegistry reactiveAdapterRegistry , Duration blockTimeout ) {
55
62
56
- this .argumentResolvers = argumentResolvers ;
57
63
this .clientAdapter = clientAdapter ;
64
+ this .argumentResolvers = argumentResolvers ;
58
65
this .reactiveAdapterRegistry = reactiveAdapterRegistry ;
59
66
this .blockTimeout = blockTimeout ;
60
67
}
61
68
62
69
63
70
/**
64
- * Create a proxy for executing requests to the given HTTP service.
71
+ * Return a proxy that implements the given HTTP service interface to perform
72
+ * HTTP requests and retrieves responses through an HTTP client.
65
73
* @param serviceType the HTTP service to create a proxy for
66
- * @param <S> the service type
74
+ * @param <S> the HTTP service type
67
75
* @return the created proxy
68
76
*/
69
77
public <S > S createClient (Class <S > serviceType ) {
70
78
71
79
List <HttpServiceMethod > methods =
72
- MethodIntrospector .selectMethods (serviceType , this ::isHttpRequestMethod )
80
+ MethodIntrospector .selectMethods (serviceType , this ::isExchangeMethod )
73
81
.stream ()
74
- .map (method -> initServiceMethod (method , serviceType ))
82
+ .map (method ->
83
+ new HttpServiceMethod (
84
+ method , serviceType , this .argumentResolvers ,
85
+ this .clientAdapter , this .reactiveAdapterRegistry , this .blockTimeout ))
75
86
.toList ();
76
87
77
88
return ProxyFactory .getProxy (serviceType , new HttpServiceMethodInterceptor (methods ));
78
89
}
79
90
80
- private boolean isHttpRequestMethod (Method method ) {
91
+ private boolean isExchangeMethod (Method method ) {
81
92
return AnnotatedElementUtils .hasAnnotation (method , HttpExchange .class );
82
93
}
83
94
84
- private HttpServiceMethod initServiceMethod (Method method , Class <?> serviceType ) {
85
- return new HttpServiceMethod (
86
- method , serviceType , this .argumentResolvers ,
87
- this .clientAdapter , this .reactiveAdapterRegistry , this .blockTimeout );
95
+
96
+ /**
97
+ * Return a builder for an {@link HttpServiceProxyFactory}.
98
+ * @param adapter an adapter for the underlying HTTP client
99
+ * @return the builder
100
+ */
101
+ public static Builder builder (HttpClientAdapter adapter ) {
102
+ return new Builder (adapter );
103
+ }
104
+
105
+
106
+ /**
107
+ * Builder for {@link HttpServiceProxyFactory}.
108
+ */
109
+ public final static class Builder {
110
+
111
+ private final HttpClientAdapter clientAdapter ;
112
+
113
+ private final List <HttpServiceArgumentResolver > customResolvers = new ArrayList <>();
114
+
115
+ @ Nullable
116
+ private ConversionService conversionService ;
117
+
118
+ private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry .getSharedInstance ();
119
+
120
+ private Duration blockTimeout = Duration .ofSeconds (5 );
121
+
122
+ private Builder (HttpClientAdapter clientAdapter ) {
123
+ Assert .notNull (clientAdapter , "HttpClientAdapter is required" );
124
+ this .clientAdapter = clientAdapter ;
125
+ }
126
+
127
+ /**
128
+ * Register a custom argument resolver. This will be inserted ahead of
129
+ * default resolvers.
130
+ * @return the same builder instance
131
+ */
132
+ public Builder addCustomResolver (HttpServiceArgumentResolver resolver ) {
133
+ this .customResolvers .add (resolver );
134
+ return this ;
135
+ }
136
+
137
+ /**
138
+ * Set the {@link ConversionService} to use where input values need to
139
+ * be formatted as Strings.
140
+ * <p>By default this is {@link DefaultFormattingConversionService}.
141
+ * @return the same builder instance
142
+ */
143
+ public Builder setConversionService (ConversionService conversionService ) {
144
+ this .conversionService = conversionService ;
145
+ return this ;
146
+ }
147
+
148
+ /**
149
+ * Set the {@link ReactiveAdapterRegistry} to use to support different
150
+ * asynchronous types for HTTP Service method return values.
151
+ * <p>By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}.
152
+ * @return the same builder instance
153
+ */
154
+ public Builder setReactiveAdapterRegistry (ReactiveAdapterRegistry registry ) {
155
+ this .reactiveAdapterRegistry = registry ;
156
+ return this ;
157
+ }
158
+
159
+ /**
160
+ * Configure how long to wait for a response for an HTTP Service method
161
+ * with a synchronous (blocking) method signature.
162
+ * <p>By default this is 5 seconds.
163
+ * @param blockTimeout the timeout value
164
+ * @return the same builder instance
165
+ */
166
+ public Builder setBlockTimeout (Duration blockTimeout ) {
167
+ this .blockTimeout = blockTimeout ;
168
+ return this ;
169
+ }
170
+
171
+ /**
172
+ * Build and return the {@link HttpServiceProxyFactory} instance.
173
+ */
174
+ public HttpServiceProxyFactory build () {
175
+
176
+ ConversionService conversionService = initConversionService ();
177
+ List <HttpServiceArgumentResolver > resolvers = initArgumentResolvers (conversionService );
178
+
179
+ return new HttpServiceProxyFactory (
180
+ this .clientAdapter , resolvers , this .reactiveAdapterRegistry , this .blockTimeout );
181
+ }
182
+
183
+ private ConversionService initConversionService () {
184
+ return (this .conversionService != null ?
185
+ this .conversionService : new DefaultFormattingConversionService ());
186
+ }
187
+
188
+ private List <HttpServiceArgumentResolver > initArgumentResolvers (ConversionService conversionService ) {
189
+ List <HttpServiceArgumentResolver > resolvers = new ArrayList <>(this .customResolvers );
190
+ resolvers .add (new HttpMethodArgumentResolver ());
191
+ resolvers .add (new PathVariableArgumentResolver (conversionService ));
192
+ return resolvers ;
193
+ }
194
+
88
195
}
89
196
90
197
@@ -93,10 +200,11 @@ private HttpServiceMethod initServiceMethod(Method method, Class<?> serviceType)
93
200
*/
94
201
private static final class HttpServiceMethodInterceptor implements MethodInterceptor {
95
202
96
- private final Map <Method , HttpServiceMethod > httpServiceMethods = new HashMap <>() ;
203
+ private final Map <Method , HttpServiceMethod > httpServiceMethods ;
97
204
98
205
private HttpServiceMethodInterceptor (List <HttpServiceMethod > methods ) {
99
- methods .forEach (serviceMethod -> this .httpServiceMethods .put (serviceMethod .getMethod (), serviceMethod ));
206
+ this .httpServiceMethods = methods .stream ()
207
+ .collect (Collectors .toMap (HttpServiceMethod ::getMethod , Function .identity ()));
100
208
}
101
209
102
210
@ Override
@@ -105,7 +213,6 @@ public Object invoke(MethodInvocation invocation) {
105
213
HttpServiceMethod httpServiceMethod = this .httpServiceMethods .get (method );
106
214
return httpServiceMethod .invoke (invocation .getArguments ());
107
215
}
108
-
109
216
}
110
217
111
218
}
0 commit comments