From 9c34afa6f737fbfdfe8566934f21cc80e879a332 Mon Sep 17 00:00:00 2001 From: ballballbaba <43091827+TaoJing96@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:36:35 +0800 Subject: [PATCH] support method option and add UT (#1881) * support method option and add UT * format code style * add Experimental annotation * Added @Experimental to new method --------- Co-authored-by: Marvin Froeder --- core/src/main/java/feign/Request.java | 33 +++++++++++++ .../java/feign/SynchronousMethodHandler.java | 4 +- core/src/main/java/feign/Util.java | 9 ++++ core/src/test/java/feign/OptionsTest.java | 48 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/feign/Request.java b/core/src/main/java/feign/Request.java index f7eae9726..d119aa05e 100644 --- a/core/src/main/java/feign/Request.java +++ b/core/src/main/java/feign/Request.java @@ -14,6 +14,7 @@ package feign; import static feign.Util.checkNotNull; +import static feign.Util.getThreadIdentifier; import static feign.Util.valuesOrEmpty; import java.io.Serializable; @@ -23,8 +24,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** An immutable request to an http server. */ @@ -320,6 +323,35 @@ public static class Options { private final long readTimeout; private final TimeUnit readTimeoutUnit; private final boolean followRedirects; + private final Map> threadToMethodOptions; + + /** + * Get an Options by methodName + * + * @param methodName it's your FeignInterface method name. + * @return method Options + */ + @Experimental + public Options getMethodOptions(String methodName) { + Map methodOptions = + threadToMethodOptions.getOrDefault(getThreadIdentifier(), new HashMap<>()); + return methodOptions.getOrDefault(methodName, this); + } + + /** + * Set methodOptions by methodKey and options + * + * @param methodName it's your FeignInterface method name. + * @param options it's the Options for this method. + */ + @Experimental + public void setMethodOptions(String methodName, Options options) { + String threadIdentifier = getThreadIdentifier(); + Map methodOptions = + threadToMethodOptions.getOrDefault(threadIdentifier, new HashMap<>()); + threadToMethodOptions.put(threadIdentifier, methodOptions); + methodOptions.put(methodName, options); + } /** * Creates a new Options instance. @@ -360,6 +392,7 @@ public Options( this.readTimeout = readTimeout; this.readTimeoutUnit = readTimeoutUnit; this.followRedirects = followRedirects; + this.threadToMethodOptions = new ConcurrentHashMap<>(); } /** diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 2fa4429cc..6f9c03f3b 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -130,13 +130,13 @@ Request targetRequest(RequestTemplate template) { Options findOptions(Object[] argv) { if (argv == null || argv.length == 0) { - return this.options; + return this.options.getMethodOptions(metadata.method().getName()); } return Stream.of(argv) .filter(Options.class::isInstance) .map(Options.class::cast) .findFirst() - .orElse(this.options); + .orElse(this.options.getMethodOptions(metadata.method().getName())); } static class Factory implements MethodHandler.Factory { diff --git a/core/src/main/java/feign/Util.java b/core/src/main/java/feign/Util.java index 12c939fce..3a4bb2b16 100644 --- a/core/src/main/java/feign/Util.java +++ b/core/src/main/java/feign/Util.java @@ -359,4 +359,13 @@ public static List allFields(Class clazz) { fields.addAll(allFields(clazz.getSuperclass())); return fields; } + + public static String getThreadIdentifier() { + Thread currentThread = Thread.currentThread(); + return currentThread.getThreadGroup() + + "_" + + currentThread.getName() + + "_" + + currentThread.getId(); + } } diff --git a/core/src/test/java/feign/OptionsTest.java b/core/src/test/java/feign/OptionsTest.java index d118a8d8a..ad971038f 100644 --- a/core/src/test/java/feign/OptionsTest.java +++ b/core/src/test/java/feign/OptionsTest.java @@ -16,7 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.net.SocketTimeoutException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.hamcrest.CoreMatchers; @@ -90,4 +92,50 @@ public void normalResponseForChildOptionsTest() { assertThat(api.getChildOptions(new ChildOptions(1000, 4 * 1000))).isEqualTo("foo"); } + + @Test + public void socketTimeoutWithMethodOptionsTest() throws Exception { + final MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody("foo").setBodyDelay(2, TimeUnit.SECONDS)); + Request.Options options = new Request.Options(1000, 3000); + final OptionsInterface api = + Feign.builder().options(options).target(OptionsInterface.class, server.url("/").toString()); + + AtomicReference exceptionAtomicReference = new AtomicReference<>(); + Thread thread = + new Thread( + () -> { + try { + options.setMethodOptions("get", new Request.Options(1000, 1000)); + api.get(); + } catch (Exception exception) { + exceptionAtomicReference.set(exception); + } + }); + thread.start(); + thread.join(); + thrown.expect(FeignException.class); + thrown.expectCause(CoreMatchers.isA(SocketTimeoutException.class)); + throw exceptionAtomicReference.get(); + } + + @Test + public void normalResponseWithMethodOptionsTest() throws Exception { + final MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody("foo").setBodyDelay(2, TimeUnit.SECONDS)); + Request.Options options = new Request.Options(1000, 1000); + final OptionsInterface api = + Feign.builder().options(options).target(OptionsInterface.class, server.url("/").toString()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + Thread thread = + new Thread( + () -> { + options.setMethodOptions("get", new Request.Options(1000, 3000)); + api.get(); + countDownLatch.countDown(); + }); + thread.start(); + thread.join(); + } }