Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Version 8.14
* Add support for RxJava Observable and Single return types via the `HystrixFeign` builder.
* Adds fallback implementation configuration to the `HystrixFeign` builder

### Version 8.13
* Never expands >8kb responses into memory
Expand Down
31 changes: 30 additions & 1 deletion hystrix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,33 @@ api.getYourType("a").execute();

// or to apply hystrix to existing feign methods.
api.getYourTypeSynchronous("a");
```
```

### Fallback support

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);
```
203 changes: 194 additions & 9 deletions hystrix/src/main/java/feign/hystrix/HystrixFeign.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,214 @@

import com.netflix.hystrix.HystrixCommand;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Logger;
import feign.Request;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.Target;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;

/**
* Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects.
* Also decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()} directly.
* Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects. Also
* decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()}
* directly.
*/
public final class HystrixFeign {

public static Builder builder() {
return new Builder();
}

public static final class Builder extends Feign.Builder {
// Doesn't extend Feign.Builder for two reasons:
// * Hide invocationHandlerFactory - as this isn't customizable
// * Provide a path to the new fallback method w/o using covariant return types
public static final class Builder {
private final Feign.Builder delegate = new Feign.Builder();
private Contract contract = new Contract.Default();

/**
* @see #target(Class, String, Object)
*/
public <T> T target(Target<T> target, final T fallback) {
delegate.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, fallback);
}
});
delegate.contract(new HystrixDelegatingContract(contract));
return delegate.build().newInstance(target);
}

/**
* Like {@link Feign#newInstance(Target)}, except with {@link HystrixCommand#getFallback()
* fallback} support.
*
* <p>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.
*
* Here's an example:
* <pre>
* {@code
*
* // 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);
* }</pre>
*
* @see #target(Target, Object)
*/
public <T> T target(Class<T> apiType, String url, T fallback) {
return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
}

/**
* @see feign.Feign.Builder#contract
*/
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}

/**
* @see feign.Feign.Builder#build
*/
public Feign build() {
delegate.invocationHandlerFactory(new HystrixInvocationHandler.Factory());
delegate.contract(new HystrixDelegatingContract(contract));
return delegate.build();
}

// re-declaring methods in Feign.Builder is same work as covariant overrides,
// but results in less complex bytecode.

/**
* @see feign.Feign.Builder#target(Class, String)
*/
public <T> T target(Class<T> apiType, String url) {
return target(new Target.HardCodedTarget<T>(apiType, url));
}

/**
* @see feign.Feign.Builder#target(Target)
*/
public <T> T target(Target<T> target) {
return build().newInstance(target);
}

/**
* @see feign.Feign.Builder#logLevel
*/
public Builder logLevel(Logger.Level logLevel) {
delegate.logLevel(logLevel);
return this;
}

/**
* @see feign.Feign.Builder#client
*/
public Builder client(Client client) {
delegate.client(client);
return this;
}

/**
* @see feign.Feign.Builder#retryer
*/
public Builder retryer(Retryer retryer) {
delegate.retryer(retryer);
return this;
}

/**
* @see feign.Feign.Builder#retryer
*/
public Builder logger(Logger logger) {
delegate.logger(logger);
return this;
}

/**
* @see feign.Feign.Builder#encoder
*/
public Builder encoder(Encoder encoder) {
delegate.encoder(encoder);
return this;
}

/**
* @see feign.Feign.Builder#decoder
*/
public Builder decoder(Decoder decoder) {
delegate.decoder(decoder);
return this;
}

/**
* @see feign.Feign.Builder#decode404
*/
public Builder decode404() {
delegate.decode404();
return this;
}

/**
* @see feign.Feign.Builder#errorDecoder
*/
public Builder errorDecoder(ErrorDecoder errorDecoder) {
delegate.errorDecoder(errorDecoder);
return this;
}

/**
* @see feign.Feign.Builder#options
*/
public Builder options(Request.Options options) {
delegate.options(options);
return this;
}

public Builder() {
invocationHandlerFactory(new HystrixInvocationHandler.Factory());
contract(new HystrixDelegatingContract(new Contract.Default()));
/**
* @see feign.Feign.Builder#requestInterceptor
*/
public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
delegate.requestInterceptor(requestInterceptor);
return this;
}

@Override
public Feign.Builder contract(Contract contract) {
return super.contract(new HystrixDelegatingContract(contract));
/**
* @see feign.Feign.Builder#requestInterceptors
*/
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
delegate.requestInterceptors(requestInterceptors);
return this;
}
}
}
31 changes: 24 additions & 7 deletions hystrix/src/main/java/feign/hystrix/HystrixInvocationHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,33 @@
*/
package feign.hystrix;

import static feign.Util.checkNotNull;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;

import feign.InvocationHandlerFactory;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Target;
import rx.Observable;
import rx.Single;

import static feign.Util.checkNotNull;

final class HystrixInvocationHandler implements InvocationHandler {

private final Target<?> target;
private final Map<Method, MethodHandler> dispatch;
private final Object fallback; // Nullable

HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch) {
HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch, Object fallback) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallback = fallback;
}

@Override
Expand All @@ -60,6 +63,20 @@ protected Object run() throws Exception {
throw (Error)t;
}
}

@Override
protected Object getFallback() {
if (fallback == null) return super.getFallback();
try {
return method.invoke(fallback, args);
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};

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

@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch);
return new HystrixInvocationHandler(target, dispatch, null);
}
}
}
Loading