Skip to content

Commit f774a42

Browse files
author
Adrian Cole
committed
Fixes dispatch for toString, equals, hashCode in hystrix
This copies missing logic from `FeignInvocationHandler` to `HystrixInvocationHandler`, preventing NPEs when calling methods defined on `java.lang.Object`.
1 parent 70f0d97 commit f774a42

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

hystrix/src/main/java/feign/hystrix/HystrixInvocationHandler.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
import java.lang.reflect.InvocationHandler;
2323
import java.lang.reflect.InvocationTargetException;
2424
import java.lang.reflect.Method;
25-
import java.util.HashMap;
25+
import java.lang.reflect.Proxy;
26+
import java.util.LinkedHashMap;
2627
import java.util.Map;
2728

2829
import feign.InvocationHandlerFactory;
@@ -56,7 +57,7 @@ final class HystrixInvocationHandler implements InvocationHandler {
5657
* @return cached methods map for fallback invoking
5758
*/
5859
private Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch) {
59-
Map<Method, Method> result = new HashMap<Method, Method>();
60+
Map<Method, Method> result = new LinkedHashMap<Method, Method>();
6061
for (Method method : dispatch.keySet()) {
6162
method.setAccessible(true);
6263
result.put(method, method);
@@ -67,6 +68,22 @@ private Map<Method, Method> toFallbackMethod(Map<Method, MethodHandler> dispatch
6768
@Override
6869
public Object invoke(final Object proxy, final Method method, final Object[] args)
6970
throws Throwable {
71+
// early exit if the invoked method is from java.lang.Object
72+
// code is the same as ReflectiveFeign.FeignInvocationHandler
73+
if ("equals".equals(method.getName())) {
74+
try {
75+
Object otherHandler =
76+
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
77+
return equals(otherHandler);
78+
} catch (IllegalArgumentException e) {
79+
return false;
80+
}
81+
} else if ("hashCode".equals(method.getName())) {
82+
return hashCode();
83+
} else if ("toString".equals(method.getName())) {
84+
return toString();
85+
}
86+
7087
String groupKey = this.target.name();
7188
String commandKey = method.getName();
7289
HystrixCommand.Setter setter = HystrixCommand.Setter
@@ -137,6 +154,25 @@ private boolean isReturnsSingle(Method method) {
137154
return Single.class.isAssignableFrom(method.getReturnType());
138155
}
139156

157+
@Override
158+
public boolean equals(Object obj) {
159+
if (obj instanceof HystrixInvocationHandler) {
160+
HystrixInvocationHandler other = (HystrixInvocationHandler) obj;
161+
return target.equals(other.target);
162+
}
163+
return false;
164+
}
165+
166+
@Override
167+
public int hashCode() {
168+
return target.hashCode();
169+
}
170+
171+
@Override
172+
public String toString() {
173+
return target.toString();
174+
}
175+
140176
static final class Factory implements InvocationHandlerFactory {
141177

142178
@Override

hystrix/src/test/java/feign/hystrix/HystrixBuilderTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import feign.Headers;
2121
import feign.Param;
2222
import feign.RequestLine;
23+
import feign.Target;
24+
import feign.Target.HardCodedTarget;
2325
import feign.gson.GsonDecoder;
2426
import rx.Observable;
2527
import rx.Single;
@@ -426,13 +428,58 @@ public void plainListFallback() {
426428
assertThat(list).isNotNull().containsExactly("fallback");
427429
}
428430

431+
@Test
432+
public void equalsHashCodeAndToStringWork() {
433+
Target<TestInterface> t1 =
434+
new HardCodedTarget<TestInterface>(TestInterface.class, "http://localhost:8080");
435+
Target<TestInterface> t2 =
436+
new HardCodedTarget<TestInterface>(TestInterface.class, "http://localhost:8888");
437+
Target<OtherTestInterface> t3 =
438+
new HardCodedTarget<OtherTestInterface>(OtherTestInterface.class, "http://localhost:8080");
439+
TestInterface i1 = HystrixFeign.builder().target(t1);
440+
TestInterface i2 = HystrixFeign.builder().target(t1);
441+
TestInterface i3 = HystrixFeign.builder().target(t2);
442+
OtherTestInterface i4 = HystrixFeign.builder().target(t3);
443+
444+
assertThat(i1)
445+
.isEqualTo(i2)
446+
.isNotEqualTo(i3)
447+
.isNotEqualTo(i4);
448+
449+
assertThat(i1.hashCode())
450+
.isEqualTo(i2.hashCode())
451+
.isNotEqualTo(i3.hashCode())
452+
.isNotEqualTo(i4.hashCode());
453+
454+
assertThat(i1.toString())
455+
.isEqualTo(i2.toString())
456+
.isNotEqualTo(i3.toString())
457+
.isNotEqualTo(i4.toString());
458+
459+
assertThat(t1)
460+
.isNotEqualTo(i1);
461+
462+
assertThat(t1.hashCode())
463+
.isEqualTo(i1.hashCode());
464+
465+
assertThat(t1.toString())
466+
.isEqualTo(i1.toString());
467+
}
468+
429469
private TestInterface target() {
430470
return HystrixFeign.builder()
431471
.decoder(new GsonDecoder())
432472
.target(TestInterface.class, "http://localhost:" + server.getPort(),
433473
new FallbackTestInterface());
434474
}
435475

476+
interface OtherTestInterface {
477+
478+
@RequestLine("GET /")
479+
@Headers("Accept: application/json")
480+
HystrixCommand<List<String>> listCommand();
481+
}
482+
436483
interface TestInterface {
437484

438485
@RequestLine("GET /")

0 commit comments

Comments
 (0)