Skip to content

Commit 122f9a5

Browse files
author
Adrian Cole
committed
Adds fallback implementation configuration to the HystrixFeign builder
Fallbacks are known values, which you return when there's an error invoking an http method. For example, you can return a cached result as opposed to raising an error to the caller. To use this feature, pass a safe implementation of your target interface as the last parameter to `HystrixFeign.Builder.target`. Here's an example: ```java // When dealing with fallbacks, it is less tedious to keep interfaces small. interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<String> contributors(@param("owner") String owner, @param("repo") String repo); } // This instance will be invoked if there are errors of any kind. GitHub fallback = (owner, repo) -> { if (owner.equals("Netflix") && repo.equals("feign")) { return Arrays.asList("stuarthendren"); // inspired this approach! } else { return Collections.emptyList(); } }; GitHub github = HystrixFeign.builder() ... .target(GitHub.class, "https://api.github.com", fallback); ``` Credit to the idea goes to @stuarthendren!
1 parent 7c0ddbf commit 122f9a5

5 files changed

Lines changed: 329 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Version 8.14
22
* Add support for RxJava Observable and Single return types via the `HystrixFeign` builder.
3+
* Adds fallback implementation configuration to the `HystrixFeign` builder
34

45
### Version 8.13
56
* Never expands >8kb responses into memory

hystrix/README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,33 @@ api.getYourType("a").execute();
4848

4949
// or to apply hystrix to existing feign methods.
5050
api.getYourTypeSynchronous("a");
51-
```
51+
```
52+
53+
### Fallback support
54+
55+
Fallbacks are known values, which you return when there's an error invoking an http method.
56+
For example, you can return a cached result as opposed to raising an error to the caller. To use
57+
this feature, pass a safe implementation of your target interface as the last parameter to `HystrixFeign.Builder.target`.
58+
59+
Here's an example:
60+
61+
```java
62+
// When dealing with fallbacks, it is less tedious to keep interfaces small.
63+
interface GitHub {
64+
@RequestLine("GET /repos/{owner}/{repo}/contributors")
65+
List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
66+
}
67+
68+
// This instance will be invoked if there are errors of any kind.
69+
GitHub fallback = (owner, repo) -> {
70+
if (owner.equals("Netflix") && repo.equals("feign")) {
71+
return Arrays.asList("stuarthendren"); // inspired this approach!
72+
} else {
73+
return Collections.emptyList();
74+
}
75+
};
76+
77+
GitHub github = HystrixFeign.builder()
78+
...
79+
.target(GitHub.class, "https://api.github.com", fallback);
80+
```

hystrix/src/main/java/feign/hystrix/HystrixFeign.java

Lines changed: 194 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,214 @@
22

33
import com.netflix.hystrix.HystrixCommand;
44

5+
import java.lang.reflect.InvocationHandler;
6+
import java.lang.reflect.Method;
7+
import java.util.Map;
8+
9+
import feign.Client;
510
import feign.Contract;
611
import feign.Feign;
12+
import feign.InvocationHandlerFactory;
13+
import feign.Logger;
14+
import feign.Request;
15+
import feign.RequestInterceptor;
16+
import feign.Retryer;
17+
import feign.Target;
18+
import feign.codec.Decoder;
19+
import feign.codec.Encoder;
20+
import feign.codec.ErrorDecoder;
721

822
/**
9-
* Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects.
10-
* Also decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()} directly.
23+
* Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects. Also
24+
* decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()}
25+
* directly.
1126
*/
1227
public final class HystrixFeign {
1328

1429
public static Builder builder() {
1530
return new Builder();
1631
}
1732

18-
public static final class Builder extends Feign.Builder {
33+
// Doesn't extend Feign.Builder for two reasons:
34+
// * Hide invocationHandlerFactory - as this isn't customizable
35+
// * Provide a path to the new fallback method w/o using covariant return types
36+
public static final class Builder {
37+
private final Feign.Builder delegate = new Feign.Builder();
38+
private Contract contract = new Contract.Default();
39+
40+
/**
41+
* @see #target(Class, String, Object)
42+
*/
43+
public <T> T target(Target<T> target, final T fallback) {
44+
delegate.invocationHandlerFactory(new InvocationHandlerFactory() {
45+
@Override
46+
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
47+
return new HystrixInvocationHandler(target, dispatch, fallback);
48+
}
49+
});
50+
delegate.contract(new HystrixDelegatingContract(contract));
51+
return delegate.build().newInstance(target);
52+
}
53+
54+
/**
55+
* Like {@link Feign#newInstance(Target)}, except with {@link HystrixCommand#getFallback()
56+
* fallback} support.
57+
*
58+
* <p>Fallbacks are known values, which you return when there's an error invoking an http
59+
* method. For example, you can return a cached result as opposed to raising an error to the
60+
* caller. To use this feature, pass a safe implementation of your target interface as the last
61+
* parameter.
62+
*
63+
* Here's an example:
64+
* <pre>
65+
* {@code
66+
*
67+
* // When dealing with fallbacks, it is less tedious to keep interfaces small.
68+
* interface GitHub {
69+
* @RequestLine("GET /repos/{owner}/{repo}/contributors")
70+
* List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
71+
* }
72+
*
73+
* // This instance will be invoked if there are errors of any kind.
74+
* GitHub fallback = (owner, repo) -> {
75+
* if (owner.equals("Netflix") && repo.equals("feign")) {
76+
* return Arrays.asList("stuarthendren"); // inspired this approach!
77+
* } else {
78+
* return Collections.emptyList();
79+
* }
80+
* };
81+
*
82+
* GitHub github = HystrixFeign.builder()
83+
* ...
84+
* .target(GitHub.class, "https://api.github.com", fallback);
85+
* }</pre>
86+
*
87+
* @see #target(Target, Object)
88+
*/
89+
public <T> T target(Class<T> apiType, String url, T fallback) {
90+
return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
91+
}
92+
93+
/**
94+
* @see feign.Feign.Builder#contract
95+
*/
96+
public Builder contract(Contract contract) {
97+
this.contract = contract;
98+
return this;
99+
}
100+
101+
/**
102+
* @see feign.Feign.Builder#build
103+
*/
104+
public Feign build() {
105+
delegate.invocationHandlerFactory(new HystrixInvocationHandler.Factory());
106+
delegate.contract(new HystrixDelegatingContract(contract));
107+
return delegate.build();
108+
}
109+
110+
// re-declaring methods in Feign.Builder is same work as covariant overrides,
111+
// but results in less complex bytecode.
112+
113+
/**
114+
* @see feign.Feign.Builder#target(Class, String)
115+
*/
116+
public <T> T target(Class<T> apiType, String url) {
117+
return target(new Target.HardCodedTarget<T>(apiType, url));
118+
}
119+
120+
/**
121+
* @see feign.Feign.Builder#target(Target)
122+
*/
123+
public <T> T target(Target<T> target) {
124+
return build().newInstance(target);
125+
}
126+
127+
/**
128+
* @see feign.Feign.Builder#logLevel
129+
*/
130+
public Builder logLevel(Logger.Level logLevel) {
131+
delegate.logLevel(logLevel);
132+
return this;
133+
}
134+
135+
/**
136+
* @see feign.Feign.Builder#client
137+
*/
138+
public Builder client(Client client) {
139+
delegate.client(client);
140+
return this;
141+
}
142+
143+
/**
144+
* @see feign.Feign.Builder#retryer
145+
*/
146+
public Builder retryer(Retryer retryer) {
147+
delegate.retryer(retryer);
148+
return this;
149+
}
150+
151+
/**
152+
* @see feign.Feign.Builder#retryer
153+
*/
154+
public Builder logger(Logger logger) {
155+
delegate.logger(logger);
156+
return this;
157+
}
158+
159+
/**
160+
* @see feign.Feign.Builder#encoder
161+
*/
162+
public Builder encoder(Encoder encoder) {
163+
delegate.encoder(encoder);
164+
return this;
165+
}
166+
167+
/**
168+
* @see feign.Feign.Builder#decoder
169+
*/
170+
public Builder decoder(Decoder decoder) {
171+
delegate.decoder(decoder);
172+
return this;
173+
}
174+
175+
/**
176+
* @see feign.Feign.Builder#decode404
177+
*/
178+
public Builder decode404() {
179+
delegate.decode404();
180+
return this;
181+
}
182+
183+
/**
184+
* @see feign.Feign.Builder#errorDecoder
185+
*/
186+
public Builder errorDecoder(ErrorDecoder errorDecoder) {
187+
delegate.errorDecoder(errorDecoder);
188+
return this;
189+
}
190+
191+
/**
192+
* @see feign.Feign.Builder#options
193+
*/
194+
public Builder options(Request.Options options) {
195+
delegate.options(options);
196+
return this;
197+
}
19198

20-
public Builder() {
21-
invocationHandlerFactory(new HystrixInvocationHandler.Factory());
22-
contract(new HystrixDelegatingContract(new Contract.Default()));
199+
/**
200+
* @see feign.Feign.Builder#requestInterceptor
201+
*/
202+
public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
203+
delegate.requestInterceptor(requestInterceptor);
204+
return this;
23205
}
24206

25-
@Override
26-
public Feign.Builder contract(Contract contract) {
27-
return super.contract(new HystrixDelegatingContract(contract));
207+
/**
208+
* @see feign.Feign.Builder#requestInterceptors
209+
*/
210+
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
211+
delegate.requestInterceptors(requestInterceptors);
212+
return this;
28213
}
29214
}
30215
}

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,33 @@
1515
*/
1616
package feign.hystrix;
1717

18-
import static feign.Util.checkNotNull;
18+
import com.netflix.hystrix.HystrixCommand;
19+
import com.netflix.hystrix.HystrixCommandGroupKey;
20+
import com.netflix.hystrix.HystrixCommandKey;
1921

2022
import java.lang.reflect.InvocationHandler;
23+
import java.lang.reflect.InvocationTargetException;
2124
import java.lang.reflect.Method;
2225
import java.util.Map;
2326

24-
import com.netflix.hystrix.HystrixCommand;
25-
import com.netflix.hystrix.HystrixCommandGroupKey;
26-
import com.netflix.hystrix.HystrixCommandKey;
27-
2827
import feign.InvocationHandlerFactory;
2928
import feign.InvocationHandlerFactory.MethodHandler;
3029
import feign.Target;
3130
import rx.Observable;
3231
import rx.Single;
3332

33+
import static feign.Util.checkNotNull;
34+
3435
final class HystrixInvocationHandler implements InvocationHandler {
3536

3637
private final Target<?> target;
3738
private final Map<Method, MethodHandler> dispatch;
39+
private final Object fallback; // Nullable
3840

39-
HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch) {
41+
HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch, Object fallback) {
4042
this.target = checkNotNull(target, "target");
4143
this.dispatch = checkNotNull(dispatch, "dispatch");
44+
this.fallback = fallback;
4245
}
4346

4447
@Override
@@ -60,6 +63,20 @@ protected Object run() throws Exception {
6063
throw (Error)t;
6164
}
6265
}
66+
67+
@Override
68+
protected Object getFallback() {
69+
if (fallback == null) return super.getFallback();
70+
try {
71+
return method.invoke(fallback, args);
72+
} catch (IllegalAccessException e) {
73+
// shouldn't happen as method is public due to being an interface
74+
throw new AssertionError(e);
75+
} catch (InvocationTargetException e) {
76+
// Exceptions on fallback are tossed by Hystrix
77+
throw new AssertionError(e.getCause());
78+
}
79+
}
6380
};
6481

6582
if (HystrixCommand.class.isAssignableFrom(method.getReturnType())) {
@@ -78,7 +95,7 @@ static final class Factory implements InvocationHandlerFactory {
7895

7996
@Override
8097
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
81-
return new HystrixInvocationHandler(target, dispatch);
98+
return new HystrixInvocationHandler(target, dispatch, null);
8299
}
83100
}
84101
}

0 commit comments

Comments
 (0)