Skip to content

Commit 7d1cf7c

Browse files
wplong11velo
andauthored
Refactor async feign (#1755)
* Add MethodInfoResolver to customize MethodInfo creation logic * Add methodInfoResolver setter to AsyncBuilder * Refactor CoroutineFeign to use AsyncFeignBuilder instead of FeignBuilder * Deprecate CoroutineFeign.coBuilder * Change AsyncFeign to not inherit Feign * Deprecate AsyncFeign.asyncBuilder * Refactor AsyncBuilder to build Feign directly Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
1 parent 3337825 commit 7d1cf7c

File tree

15 files changed

+164
-283
lines changed

15 files changed

+164
-283
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ interface GitHub {
10771077
10781078
public class MyApp {
10791079
public static void main(String... args) {
1080-
GitHub github = AsyncFeign.asyncBuilder()
1080+
GitHub github = AsyncFeign.builder()
10811081
.decoder(new GsonDecoder())
10821082
.target(GitHub.class, "https://api.github.com");
10831083

core/src/main/java/feign/AsyncFeign.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package feign;
1515

16+
import feign.ReflectiveFeign.ParseHandlersByName;
1617
import feign.Logger.Level;
1718
import feign.Request.Options;
1819
import feign.Target.HardCodedTarget;
@@ -44,10 +45,17 @@
4445
* be done (for example, creating and submitting a task to an {@link ExecutorService}).
4546
*/
4647
@Experimental
47-
public abstract class AsyncFeign<C> extends Feign {
48+
public abstract class AsyncFeign<C> {
49+
public static <C> AsyncBuilder<C> builder() {
50+
return new AsyncBuilder<>();
51+
}
4852

53+
/**
54+
* @deprecated use {@link #builder()} instead.
55+
*/
56+
@Deprecated()
4957
public static <C> AsyncBuilder<C> asyncBuilder() {
50-
return new AsyncBuilder<>();
58+
return builder();
5159
}
5260

5361
private static class LazyInitializedExecutorService {
@@ -66,6 +74,7 @@ public static class AsyncBuilder<C> extends BaseBuilder<AsyncBuilder<C>> {
6674
private AsyncContextSupplier<C> defaultContextSupplier = () -> null;
6775
private AsyncClient<C> client = new AsyncClient.Default<>(
6876
new Client.Default(null, null), LazyInitializedExecutorService.instance);
77+
private MethodInfoResolver methodInfoResolver = MethodInfo::new;
6978

7079
@Deprecated
7180
public AsyncBuilder<C> defaultContextSupplier(Supplier<C> supplier) {
@@ -78,6 +87,11 @@ public AsyncBuilder<C> client(AsyncClient<C> client) {
7887
return this;
7988
}
8089

90+
public AsyncBuilder<C> methodInfoResolver(MethodInfoResolver methodInfoResolver) {
91+
this.methodInfoResolver = methodInfoResolver;
92+
return this;
93+
}
94+
8195
@Override
8296
public AsyncBuilder<C> mapAndDecode(ResponseMapper mapper, Decoder decoder) {
8397
return super.mapAndDecode(mapper, decoder);
@@ -191,21 +205,19 @@ public AsyncFeign<C> build() {
191205
AsyncResponseHandler.class,
192206
capabilities);
193207

194-
195-
return new ReflectiveAsyncFeign<>(Feign.builder()
196-
.logLevel(logLevel)
197-
.client(stageExecution(activeContextHolder, client))
198-
.decoder(stageDecode(activeContextHolder, logger, logLevel, responseHandler))
199-
.forceDecoding() // force all handling through stageDecode
200-
.contract(contract)
201-
.logger(logger)
202-
.encoder(encoder)
203-
.queryMapEncoder(queryMapEncoder)
204-
.options(options)
205-
.requestInterceptors(requestInterceptors)
206-
.responseInterceptor(responseInterceptor)
207-
.invocationHandlerFactory(invocationHandlerFactory)
208-
.build(), defaultContextSupplier, activeContextHolder);
208+
final SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
209+
new SynchronousMethodHandler.Factory(stageExecution(activeContextHolder, client), retryer,
210+
requestInterceptors,
211+
responseInterceptor, logger, logLevel, dismiss404, closeAfterDecode,
212+
propagationPolicy, true);
213+
final ParseHandlersByName handlersByName =
214+
new ParseHandlersByName(contract, options, encoder,
215+
stageDecode(activeContextHolder, logger, logLevel, responseHandler), queryMapEncoder,
216+
errorDecoder, synchronousMethodHandlerFactory);
217+
final ReflectiveFeign feign =
218+
new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
219+
return new ReflectiveAsyncFeign<>(feign, defaultContextSupplier, activeContextHolder,
220+
methodInfoResolver);
209221
}
210222

211223
private Client stageExecution(
@@ -293,7 +305,6 @@ protected AsyncFeign(Feign feign, AsyncContextSupplier<C> defaultContextSupplier
293305
this.defaultContextSupplier = defaultContextSupplier;
294306
}
295307

296-
@Override
297308
public <T> T newInstance(Target<T> target) {
298309
return newInstance(target, defaultContextSupplier.newContext());
299310
}

core/src/main/java/feign/Capability.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,8 @@ default AsyncResponseHandler enrich(AsyncResponseHandler asyncResponseHandler) {
133133
default <C> AsyncContextSupplier<C> enrich(AsyncContextSupplier<C> asyncContextSupplier) {
134134
return asyncContextSupplier;
135135
}
136+
137+
default MethodInfoResolver enrich(MethodInfoResolver methodInfoResolver) {
138+
return methodInfoResolver;
139+
}
136140
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2012-2022 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign;
15+
16+
import java.lang.reflect.Method;
17+
import java.lang.reflect.ParameterizedType;
18+
import java.lang.reflect.Type;
19+
import java.util.concurrent.CompletableFuture;
20+
21+
@Experimental
22+
public interface MethodInfoResolver {
23+
public MethodInfo resolve(Class<?> targetType, Method method);
24+
}

core/src/main/java/feign/ReflectiveAsyncFeign.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
5959
}
6060

6161
final MethodInfo methodInfo =
62-
methodInfoLookup.computeIfAbsent(method, m -> new MethodInfo(type, m));
62+
methodInfoLookup.computeIfAbsent(method, m -> methodInfoResolver.resolve(type, m));
6363

6464
setInvocationContext(new AsyncInvocation<>(context, methodInfo));
6565
try {
@@ -97,11 +97,13 @@ public String toString() {
9797
}
9898

9999
private ThreadLocal<AsyncInvocation<C>> activeContextHolder;
100+
private final MethodInfoResolver methodInfoResolver;
100101

101102
public ReflectiveAsyncFeign(Feign feign, AsyncContextSupplier<C> defaultContextSupplier,
102-
ThreadLocal<AsyncInvocation<C>> contextHolder) {
103+
ThreadLocal<AsyncInvocation<C>> contextHolder, MethodInfoResolver methodInfoResolver) {
103104
super(feign, defaultContextSupplier);
104105
this.activeContextHolder = contextHolder;
106+
this.methodInfoResolver = methodInfoResolver;
105107
}
106108

107109
protected void setInvocationContext(AsyncInvocation<C> invocationContext) {

core/src/test/java/feign/AsyncFeignTest.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ public void doesntRetryAfterResponseIsSent() throws Throwable {
503503
public void throwsFeignExceptionIncludingBody() throws Throwable {
504504
server.enqueue(new MockResponse().setBody("success!"));
505505

506-
TestInterfaceAsync api = AsyncFeign.asyncBuilder().decoder((response, type) -> {
506+
TestInterfaceAsync api = AsyncFeign.builder().decoder((response, type) -> {
507507
throw new IOException("timeout");
508508
}).target(TestInterfaceAsync.class, "http://localhost:" + server.getPort());
509509

@@ -524,7 +524,7 @@ public void throwsFeignExceptionIncludingBody() throws Throwable {
524524
public void throwsFeignExceptionWithoutBody() {
525525
server.enqueue(new MockResponse().setBody("success!"));
526526

527-
TestInterfaceAsync api = AsyncFeign.asyncBuilder().decoder((response, type) -> {
527+
TestInterfaceAsync api = AsyncFeign.builder().decoder((response, type) -> {
528528
throw new IOException("timeout");
529529
}).target(TestInterfaceAsync.class, "http://localhost:" + server.getPort());
530530

@@ -549,7 +549,7 @@ public void whenReturnTypeIsResponseNoErrorHandling() throws Throwable {
549549
ExecutorService execs = Executors.newSingleThreadExecutor();
550550

551551
// fake client as Client.Default follows redirects.
552-
TestInterfaceAsync api = AsyncFeign.<Void>asyncBuilder()
552+
TestInterfaceAsync api = AsyncFeign.<Void>builder()
553553
.client(new AsyncClient.Default<>((request, options) -> response, execs))
554554
.target(TestInterfaceAsync.class, "http://localhost:" + server.getPort());
555555

@@ -625,10 +625,10 @@ public void equalsHashCodeAndToStringWork() {
625625
Target<OtherTestInterfaceAsync> t3 =
626626
new HardCodedTarget<>(OtherTestInterfaceAsync.class,
627627
"http://localhost:8080");
628-
TestInterfaceAsync i1 = AsyncFeign.asyncBuilder().target(t1);
629-
TestInterfaceAsync i2 = AsyncFeign.asyncBuilder().target(t1);
630-
TestInterfaceAsync i3 = AsyncFeign.asyncBuilder().target(t2);
631-
OtherTestInterfaceAsync i4 = AsyncFeign.asyncBuilder().target(t3);
628+
TestInterfaceAsync i1 = AsyncFeign.builder().target(t1);
629+
TestInterfaceAsync i2 = AsyncFeign.builder().target(t1);
630+
TestInterfaceAsync i3 = AsyncFeign.builder().target(t2);
631+
OtherTestInterfaceAsync i4 = AsyncFeign.builder().target(t3);
632632

633633
assertThat(i1).isEqualTo(i2).isNotEqualTo(i3).isNotEqualTo(i4);
634634

@@ -651,7 +651,7 @@ public void decodeLogicSupportsByteArray() throws Throwable {
651651
byte[] expectedResponse = {12, 34, 56};
652652
server.enqueue(new MockResponse().setBody(new Buffer().write(expectedResponse)));
653653

654-
OtherTestInterfaceAsync api = AsyncFeign.asyncBuilder().target(OtherTestInterfaceAsync.class,
654+
OtherTestInterfaceAsync api = AsyncFeign.builder().target(OtherTestInterfaceAsync.class,
655655
"http://localhost:" + server.getPort());
656656

657657
assertThat(unwrap(api.binaryResponseBody())).containsExactly(expectedResponse);
@@ -662,7 +662,7 @@ public void encodeLogicSupportsByteArray() throws Exception {
662662
byte[] expectedRequest = {12, 34, 56};
663663
server.enqueue(new MockResponse());
664664

665-
OtherTestInterfaceAsync api = AsyncFeign.asyncBuilder().target(OtherTestInterfaceAsync.class,
665+
OtherTestInterfaceAsync api = AsyncFeign.builder().target(OtherTestInterfaceAsync.class,
666666
"http://localhost:" + server.getPort());
667667

668668
CompletableFuture<?> cf = api.binaryRequestBody(expectedRequest);
@@ -733,7 +733,7 @@ public void mapAndDecodeExecutesMapFunction() throws Throwable {
733733
server.enqueue(new MockResponse().setBody("response!"));
734734

735735
TestInterfaceAsync api =
736-
AsyncFeign.asyncBuilder().mapAndDecode(upperCaseResponseMapper(), new StringDecoder())
736+
AsyncFeign.builder().mapAndDecode(upperCaseResponseMapper(), new StringDecoder())
737737
.target(TestInterfaceAsync.class, "http://localhost:" + server.getPort());
738738

739739
assertEquals("RESPONSE!", unwrap(api.post()));
@@ -962,7 +962,7 @@ public Exception decode(String methodKey, Response response) {
962962

963963
static final class TestInterfaceAsyncBuilder {
964964

965-
private final AsyncFeign.AsyncBuilder<Void> delegate = AsyncFeign.<Void>asyncBuilder()
965+
private final AsyncFeign.AsyncBuilder<Void> delegate = AsyncFeign.<Void>builder()
966966
.decoder(new Decoder.Default()).encoder(new Encoder() {
967967

968968
@SuppressWarnings("deprecation")
@@ -1018,31 +1018,31 @@ TestInterfaceAsync target(String url) {
10181018
@Test
10191019
public void testNonInterface() {
10201020
thrown.expect(IllegalArgumentException.class);
1021-
AsyncFeign.asyncBuilder().target(NonInterface.class, "http://localhost");
1021+
AsyncFeign.builder().target(NonInterface.class, "http://localhost");
10221022
}
10231023

10241024
@Test
10251025
public void testExtendedCFReturnType() {
10261026
thrown.expect(IllegalArgumentException.class);
1027-
AsyncFeign.asyncBuilder().target(ExtendedCFApi.class, "http://localhost");
1027+
AsyncFeign.builder().target(ExtendedCFApi.class, "http://localhost");
10281028
}
10291029

10301030
@Test
10311031
public void testLowerWildReturnType() {
10321032
thrown.expect(IllegalArgumentException.class);
1033-
AsyncFeign.asyncBuilder().target(LowerWildApi.class, "http://localhost");
1033+
AsyncFeign.builder().target(LowerWildApi.class, "http://localhost");
10341034
}
10351035

10361036
@Test
10371037
public void testUpperWildReturnType() {
10381038
thrown.expect(IllegalArgumentException.class);
1039-
AsyncFeign.asyncBuilder().target(UpperWildApi.class, "http://localhost");
1039+
AsyncFeign.builder().target(UpperWildApi.class, "http://localhost");
10401040
}
10411041

10421042
@Test
10431043
public void testrWildReturnType() {
10441044
thrown.expect(IllegalArgumentException.class);
1045-
AsyncFeign.asyncBuilder().target(WildApi.class, "http://localhost");
1045+
AsyncFeign.builder().target(WildApi.class, "http://localhost");
10461046
}
10471047

10481048

core/src/test/java/feign/BaseBuilderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public class BaseBuilderTest {
2626
@Test
2727
public void checkEnrichTouchesAllAsyncBuilderFields()
2828
throws IllegalArgumentException, IllegalAccessException {
29-
test(AsyncFeign.asyncBuilder().requestInterceptor(template -> {
30-
}), 13);
29+
test(AsyncFeign.builder().requestInterceptor(template -> {
30+
}), 14);
3131
}
3232

3333
private void test(BaseBuilder<?> builder, int expectedFieldsCount)

core/src/test/java/feign/FeignUnderAsyncTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ public Object decode(Response response, Type type) throws IOException {
487487
public void throwsFeignExceptionIncludingBody() {
488488
server.enqueue(new MockResponse().setBody("success!"));
489489

490-
TestInterface api = AsyncFeign.asyncBuilder()
490+
TestInterface api = AsyncFeign.builder()
491491
.decoder((response, type) -> {
492492
throw new IOException("timeout");
493493
})
@@ -506,7 +506,7 @@ public void throwsFeignExceptionIncludingBody() {
506506
public void throwsFeignExceptionWithoutBody() {
507507
server.enqueue(new MockResponse().setBody("success!"));
508508

509-
TestInterface api = AsyncFeign.asyncBuilder()
509+
TestInterface api = AsyncFeign.builder()
510510
.decoder((response, type) -> {
511511
throw new IOException("timeout");
512512
})
@@ -536,7 +536,7 @@ public void whenReturnTypeIsResponseNoErrorHandling() {
536536
ExecutorService execs = Executors.newSingleThreadExecutor();
537537

538538
// fake client as Client.Default follows redirects.
539-
TestInterface api = AsyncFeign.<Void>asyncBuilder()
539+
TestInterface api = AsyncFeign.<Void>builder()
540540
.client(new AsyncClient.Default<>((request, options) -> response, execs))
541541
.target(TestInterface.class, "http://localhost:" + server.getPort());
542542

@@ -635,10 +635,10 @@ public void equalsHashCodeAndToStringWork() {
635635
new HardCodedTarget<TestInterface>(TestInterface.class, "http://localhost:8888");
636636
Target<OtherTestInterface> t3 =
637637
new HardCodedTarget<OtherTestInterface>(OtherTestInterface.class, "http://localhost:8080");
638-
TestInterface i1 = AsyncFeign.asyncBuilder().target(t1);
639-
TestInterface i2 = AsyncFeign.asyncBuilder().target(t1);
640-
TestInterface i3 = AsyncFeign.asyncBuilder().target(t2);
641-
OtherTestInterface i4 = AsyncFeign.asyncBuilder().target(t3);
638+
TestInterface i1 = AsyncFeign.builder().target(t1);
639+
TestInterface i2 = AsyncFeign.builder().target(t1);
640+
TestInterface i3 = AsyncFeign.builder().target(t2);
641+
OtherTestInterface i4 = AsyncFeign.builder().target(t3);
642642

643643
assertThat(i1)
644644
.isEqualTo(i2)
@@ -671,7 +671,7 @@ public void decodeLogicSupportsByteArray() throws Exception {
671671
server.enqueue(new MockResponse().setBody(new Buffer().write(expectedResponse)));
672672

673673
OtherTestInterface api =
674-
AsyncFeign.asyncBuilder().target(OtherTestInterface.class,
674+
AsyncFeign.builder().target(OtherTestInterface.class,
675675
"http://localhost:" + server.getPort());
676676

677677
assertThat(api.binaryResponseBody())
@@ -684,7 +684,7 @@ public void encodeLogicSupportsByteArray() throws Exception {
684684
server.enqueue(new MockResponse());
685685

686686
OtherTestInterface api =
687-
AsyncFeign.asyncBuilder().target(OtherTestInterface.class,
687+
AsyncFeign.builder().target(OtherTestInterface.class,
688688
"http://localhost:" + server.getPort());
689689

690690
api.binaryRequestBody(expectedRequest);
@@ -743,7 +743,7 @@ private Response responseWithText(String text) {
743743
public void mapAndDecodeExecutesMapFunction() throws Exception {
744744
server.enqueue(new MockResponse().setBody("response!"));
745745

746-
TestInterface api = AsyncFeign.asyncBuilder()
746+
TestInterface api = AsyncFeign.builder()
747747
.mapAndDecode(upperCaseResponseMapper(), new StringDecoder())
748748
.target(TestInterface.class, "http://localhost:" + server.getPort());
749749

@@ -967,7 +967,7 @@ public Exception decode(String methodKey, Response response) {
967967

968968
static final class TestInterfaceBuilder {
969969

970-
private final AsyncFeign.AsyncBuilder<Void> delegate = AsyncFeign.<Void>asyncBuilder()
970+
private final AsyncFeign.AsyncBuilder<Void> delegate = AsyncFeign.<Void>builder()
971971
.decoder(new Decoder.Default())
972972
.encoder(new Encoder() {
973973
@Override

example-github-with-coroutine/src/main/java/example/github/GitHubExample.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ interface GitHub {
8686
fun connect(): GitHub {
8787
val decoder: Decoder = feign.gson.GsonDecoder()
8888
val encoder: Encoder = GsonEncoder()
89-
return CoroutineFeign.coBuilder<Unit>()
89+
return CoroutineFeign.builder<Unit>()
9090
.encoder(encoder)
9191
.decoder(decoder)
9292
.errorDecoder(GitHubErrorDecoder(decoder))

0 commit comments

Comments
 (0)