Browse Source

add HystrixCommand support

fixes gh-189
pull/282/head v8.11.0
Spencer Gibb 9 years ago committed by Adrian Cole
parent
commit
a4ee4264d5
  1. 3
      CHANGELOG.md
  2. 10
      README.md
  3. 10
      core/src/main/java/feign/MethodMetadata.java
  4. 40
      hystrix/README.md
  5. 13
      hystrix/build.gradle
  6. 37
      hystrix/src/main/java/feign/hystrix/HystrixDelegatingContract.java
  7. 30
      hystrix/src/main/java/feign/hystrix/HystrixFeign.java
  8. 76
      hystrix/src/main/java/feign/hystrix/HystrixInvocationHandler.java
  9. 112
      hystrix/src/test/java/feign/hystrix/HystrixBuilderTest.java
  10. 2
      settings.gradle

3
CHANGELOG.md

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
### Version 8.11
* Adds support for Hystrix via a `HystrixFeign` builder.
### Version 8.10
* Adds HTTP status to FeignException for easier response handling
* Reads class-level @Produces/@Consumes JAX-RS annotations

10
README.md

@ -152,6 +152,16 @@ MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.c @@ -152,6 +152,16 @@ MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.c
```
### Hystrix
[HystrixFeign](https://github.com/Netflix/feign/tree/master/hystrix) configures circuit breaker support provided by [Hystrix](https://github.com/Netflix/Hystrix).
To use Hystrix with Feign, add the Hystrix module to your classpath. Then use the `HystrixFeign` builder:
```java
MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
```
### SLF4J
[SLF4JModule](https://github.com/Netflix/feign/tree/master/slf4j) allows directing Feign's logging to [SLF4J](http://www.slf4j.org/), allowing you to easily use a logging backend of your choice (Logback, Log4J, etc.)

10
core/src/main/java/feign/MethodMetadata.java

@ -50,7 +50,7 @@ public final class MethodMetadata implements Serializable { @@ -50,7 +50,7 @@ public final class MethodMetadata implements Serializable {
return configKey;
}
MethodMetadata configKey(String configKey) {
public MethodMetadata configKey(String configKey) {
this.configKey = configKey;
return this;
}
@ -59,7 +59,7 @@ public final class MethodMetadata implements Serializable { @@ -59,7 +59,7 @@ public final class MethodMetadata implements Serializable {
return returnType;
}
MethodMetadata returnType(Type returnType) {
public MethodMetadata returnType(Type returnType) {
this.returnType = returnType;
return this;
}
@ -68,7 +68,7 @@ public final class MethodMetadata implements Serializable { @@ -68,7 +68,7 @@ public final class MethodMetadata implements Serializable {
return urlIndex;
}
MethodMetadata urlIndex(Integer urlIndex) {
public MethodMetadata urlIndex(Integer urlIndex) {
this.urlIndex = urlIndex;
return this;
}
@ -77,7 +77,7 @@ public final class MethodMetadata implements Serializable { @@ -77,7 +77,7 @@ public final class MethodMetadata implements Serializable {
return bodyIndex;
}
MethodMetadata bodyIndex(Integer bodyIndex) {
public MethodMetadata bodyIndex(Integer bodyIndex) {
this.bodyIndex = bodyIndex;
return this;
}
@ -89,7 +89,7 @@ public final class MethodMetadata implements Serializable { @@ -89,7 +89,7 @@ public final class MethodMetadata implements Serializable {
return bodyType;
}
MethodMetadata bodyType(Type bodyType) {
public MethodMetadata bodyType(Type bodyType) {
this.bodyType = bodyType;
return this;
}

40
hystrix/README.md

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
Hystrix
===================
This module wraps Feign's http requests in [Hystrix](https://github.com/Netflix/Hystrix/), which enables the [Circuit Breaker Pattern](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern).
To use Hystrix with Feign, add the Hystrix module to your classpath. Then, configure Feign to use the `HystrixInvocationHandler`:
```java
GitHub github = HystrixFeign.builder()
.target(GitHub.class, "https://api.github.com");
```
Methods that do *not* return [`HystrixCommand`](https://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html) are still wrapped in a `HystrixCommand`, but `execute()` is automatically called for you.
For asynchronous or reactive use, return `HystrixCommand<YourType>` rather than just `YourType`.
```java
interface YourApi {
@RequestLine("GET /yourtype/{id}")
HystrixCommand<YourType> getYourType(@Param("id") String id);
@RequestLine("GET /yourtype/{id}")
YourType getYourTypeSynchronous(@Param("id") String id);
}
YourApi api = HystrixFeign.builder()
.target(YourApi.class, "https://example.com");
// for reactive
api.getYourType("a").toObservable();
// for asynchronous
api.getYourType("a").queue();
// for synchronous
api.getYourType("a").execute();
// or to apply hystrix to existing feign methods.
api.getYourTypeSynchronous("a");
```

13
hystrix/build.gradle

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
apply plugin: 'java'
sourceCompatibility = 1.6
dependencies {
compile project(':feign-core')
compile 'com.netflix.hystrix:hystrix-core:1.4.18'
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7
testCompile 'com.squareup.okhttp:mockwebserver:2.5.0'
testCompile project(':feign-gson')
testCompile project(':feign-core').sourceSets.test.output // for assertions
}

37
hystrix/src/main/java/feign/hystrix/HystrixDelegatingContract.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
package feign.hystrix;
import static feign.Util.resolveLastTypeParameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.MethodMetadata;
final class HystrixDelegatingContract implements Contract {
private final Contract delegate;
public HystrixDelegatingContract(Contract delegate) {
this.delegate = delegate;
}
@Override
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
List<MethodMetadata> metadatas = this.delegate.parseAndValidatateMetadata(targetType);
for (MethodMetadata metadata : metadatas) {
Type type = metadata.returnType();
if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType().equals(HystrixCommand.class)) {
Type actualType = resolveLastTypeParameter(type, HystrixCommand.class);
metadata.returnType(actualType);
}
}
return metadatas;
}
}

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

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package feign.hystrix;
import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
/**
* Allows Feign interfaces to return HystrixCommand 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 {
public Builder() {
invocationHandlerFactory(new HystrixInvocationHandler.Factory());
contract(new HystrixDelegatingContract(new Contract.Default()));
}
@Override
public Feign.Builder contract(Contract contract) {
return super.contract(new HystrixDelegatingContract(contract));
}
}
}

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

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* Copyright 2015 Netflix, Inc.
*
* 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.hystrix;
import static feign.Util.checkNotNull;
import java.lang.reflect.InvocationHandler;
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;
final class HystrixInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
HystrixInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
String groupKey = this.target.name();
String commandKey = method.getName();
HystrixCommand.Setter setter = HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setter) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error)t;
}
}
};
if (HystrixCommand.class.isAssignableFrom(method.getReturnType())) {
return hystrixCommand;
}
return hystrixCommand.execute();
}
static final class Factory implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch);
}
}
}

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

@ -0,0 +1,112 @@ @@ -0,0 +1,112 @@
package feign.hystrix;
import static feign.assertj.MockWebServerAssertions.assertThat;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import com.netflix.hystrix.HystrixCommand;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import feign.Headers;
import feign.RequestLine;
import feign.gson.GsonDecoder;
public class HystrixBuilderTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Rule
public final MockWebServer server = new MockWebServer();
@Test
public void hystrixCommand() {
server.enqueue(new MockResponse().setBody("\"foo\""));
TestInterface api = target();
HystrixCommand<String> command = api.command();
assertThat(command).isNotNull();
assertThat(command.execute()).isEqualTo("foo");
}
@Test
public void hystrixCommandInt() {
server.enqueue(new MockResponse().setBody("1"));
TestInterface api = target();
HystrixCommand<Integer> command = api.intCommand();
assertThat(command).isNotNull();
assertThat(command.execute()).isEqualTo(new Integer(1));
}
@Test
public void hystrixCommandList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
TestInterface api = target();
HystrixCommand<List<String>> command = api.listCommand();
assertThat(command).isNotNull();
assertThat(command.execute()).hasSize(2).contains("foo", "bar");
}
@Test
public void plainString() {
server.enqueue(new MockResponse().setBody("\"foo\""));
TestInterface api = target();
String string = api.get();
assertThat(string).isEqualTo("foo");
}
@Test
public void plainList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
TestInterface api = target();
List<String> list = api.getList();
assertThat(list).isNotNull().hasSize(2).contains("foo", "bar");
}
private TestInterface target() {
return HystrixFeign.builder()
.decoder(new GsonDecoder())
.target(TestInterface.class, "http://localhost:" + server.getPort());
}
interface TestInterface {
@RequestLine("GET /")
@Headers("Accept: application/json")
HystrixCommand<List<String>> listCommand();
@RequestLine("GET /")
@Headers("Accept: application/json")
HystrixCommand<String> command();
@RequestLine("GET /")
@Headers("Accept: application/json")
HystrixCommand<Integer> intCommand();
@RequestLine("GET /")
@Headers("Accept: application/json")
String get();
@RequestLine("GET /")
@Headers("Accept: application/json")
List<String> getList();
}
}

2
settings.gradle

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
rootProject.name='feign'
include 'core', 'sax', 'gson', 'httpclient', 'jackson', 'jaxb', 'jaxrs', 'okhttp', 'ribbon', 'slf4j', 'jackson-jaxb'
include 'core', 'sax', 'gson', 'httpclient', 'jackson', 'jaxb', 'jaxrs', 'okhttp', 'ribbon', 'slf4j', 'jackson-jaxb', 'hystrix'
rootProject.children.each { childProject ->
childProject.name = 'feign-' + childProject.name

Loading…
Cancel
Save