diff --git a/core/src/main/java/feign/Contract.java b/core/src/main/java/feign/Contract.java index 225664687..2461587da 100644 --- a/core/src/main/java/feign/Contract.java +++ b/core/src/main/java/feign/Contract.java @@ -59,7 +59,7 @@ public List parseAndValidateMetadata(Class targetType) { for (final Method method : targetType.getMethods()) { if (method.getDeclaringClass() == Object.class || (method.getModifiers() & Modifier.STATIC) != 0 || - Util.isDefault(method)) { + Util.isDefault(method) || method.isAnnotationPresent(FeignIgnore.class)) { continue; } final MethodMetadata metadata = parseAndValidateMetadata(targetType, method); diff --git a/core/src/main/java/feign/FeignIgnore.java b/core/src/main/java/feign/FeignIgnore.java new file mode 100644 index 000000000..4ff450c2d --- /dev/null +++ b/core/src/main/java/feign/FeignIgnore.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Indicates that method will be ignored + */ +@Retention(RUNTIME) +@java.lang.annotation.Target({METHOD}) +public @interface FeignIgnore { +} diff --git a/core/src/main/java/feign/ReflectiveFeign.java b/core/src/main/java/feign/ReflectiveFeign.java index 461acc551..859c207fa 100644 --- a/core/src/main/java/feign/ReflectiveFeign.java +++ b/core/src/main/java/feign/ReflectiveFeign.java @@ -91,6 +91,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); + } else if (!dispatch.containsKey(method)) { + throw new UnsupportedOperationException(String.format("Method \"%s\" should not be called", method.getName())); } return dispatch.get(method).invoke(args); diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index 25f08c412..aadb38674 100755 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -33,6 +33,7 @@ import okio.Buffer; import org.assertj.core.data.MapEntry; import org.assertj.core.util.Maps; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -1167,6 +1168,20 @@ public void responseInterceptorChainOrder() throws Exception { assertEquals("ResponseInterceptor did not extract the response body", body, api.post()); } + @Test + public void testCallIgnoredMethod() throws Exception { + TestInterface api = new TestInterfaceBuilder() + .target("http://localhost:" + server.getPort()); + + try { + api.ignore(); + Assert.fail("No exception thrown"); + } catch (Exception e) { + assertThat(e.getClass()).isEqualTo(UnsupportedOperationException.class); + assertThat(e.getMessage()).isEqualTo("Method \"ignore\" should not be called"); + } + } + interface TestInterface { @RequestLine("POST /") @@ -1272,6 +1287,9 @@ void queryMapPropertyInheritenceWithBeanMapEncoder(@QueryMap( @Headers("Custom: {complex}") void supportComplexHttpHeaders(@Param("complex") String complex); + @FeignIgnore + String ignore(); + class ClockToMillis implements Param.Expander { @Override diff --git a/reactive/src/main/java/feign/reactive/ReactiveInvocationHandler.java b/reactive/src/main/java/feign/reactive/ReactiveInvocationHandler.java index 8cb8b0e0f..f0aad7a17 100644 --- a/reactive/src/main/java/feign/reactive/ReactiveInvocationHandler.java +++ b/reactive/src/main/java/feign/reactive/ReactiveInvocationHandler.java @@ -48,6 +48,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); + } else if (!dispatch.containsKey(method)) { + throw new UnsupportedOperationException(String.format("Method \"%s\" should not be called", method.getName())); } return this.invoke(method, this.dispatch.get(method), args); } diff --git a/reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java b/reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java index 32727871b..6c2ae57f2 100644 --- a/reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java +++ b/reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java @@ -22,21 +22,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import feign.Client; -import feign.Logger; + +import feign.*; import feign.Logger.Level; -import feign.Param; -import feign.QueryMap; -import feign.QueryMapEncoder; -import feign.Request; import feign.Request.Options; -import feign.RequestInterceptor; -import feign.RequestLine; -import feign.RequestTemplate; -import feign.Response; -import feign.ResponseMapper; -import feign.RetryableException; -import feign.Retryer; import feign.codec.Decoder; import feign.codec.ErrorDecoder; import feign.jackson.JacksonDecoder; @@ -52,6 +41,7 @@ import javax.ws.rs.Path; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -73,6 +63,20 @@ private String getServerUrl() { return "http://localhost:" + this.webServer.getPort(); } + @Test + public void testCallIgnoredMethod() throws Exception { + TestReactorService service = ReactorFeign.builder() + .target(TestReactorService.class, this.getServerUrl()); + + try { + service.ignore().subscribe(); + Assert.fail("No exception thrown"); + } catch (Exception e) { + assertThat(e.getClass()).isEqualTo(UnsupportedOperationException.class); + assertThat(e.getMessage()).isEqualTo("Method \"ignore\" should not be called"); + } + } + @Test public void testDefaultMethodsNotProxied() { TestReactorService service = ReactorFeign.builder() @@ -336,6 +340,9 @@ interface TestReactorService { @RequestLine("GET /users") Mono> usersMono(); + + @FeignIgnore + Mono ignore(); }