Skip to content

Commit be8f30d

Browse files
authored
Metric API (#1181)
* Add metrics support to feign * Introduced capabilities to hystrix * Addressing PR comments
1 parent a9e631a commit be8f30d

File tree

18 files changed

+1205
-135
lines changed

18 files changed

+1205
-135
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Copyright 2012-2020 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign;
15+
16+
import java.lang.reflect.InvocationTargetException;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import feign.Logger.Level;
20+
import feign.Request.Options;
21+
import feign.codec.Decoder;
22+
import feign.codec.Encoder;
23+
24+
/**
25+
* Capabilities expose core feign artifacts to implementations so parts of core can be customized
26+
* around the time the client being built.
27+
*
28+
* For instance, capabilities take the {@link Client}, make changes to it and feed the modified
29+
* version back to feign.
30+
*
31+
* @see Metrics5Capability
32+
*/
33+
public interface Capability {
34+
35+
36+
static <E> E enrich(E componentToEnrich, List<Capability> capabilities) {
37+
return capabilities.stream()
38+
// invoke each individual capability and feed the result to the next one.
39+
// This is equivalent to:
40+
// Capability cap1 = ...;
41+
// Capability cap2 = ...;
42+
// Capability cap2 = ...;
43+
// Contract contract = ...;
44+
// Contract contract1 = cap1.enrich(contract);
45+
// Contract contract2 = cap2.enrich(contract1);
46+
// Contract contract3 = cap3.enrich(contract2);
47+
// or in a more compact version
48+
// Contract enrichedContract = cap3.enrich(cap2.enrich(cap1.enrich(contract)));
49+
.reduce(
50+
componentToEnrich,
51+
(component, capability) -> invoke(component, capability),
52+
(component, enrichedComponent) -> enrichedComponent);
53+
}
54+
55+
static <E> E invoke(E target, Capability capability) {
56+
return Arrays.stream(capability.getClass().getMethods())
57+
.filter(method -> method.getName().equals("enrich"))
58+
.filter(method -> method.getReturnType().isInstance(target))
59+
.findFirst()
60+
.map(method -> {
61+
try {
62+
return (E) method.invoke(capability, target);
63+
} catch (IllegalAccessException | IllegalArgumentException
64+
| InvocationTargetException e) {
65+
throw new RuntimeException("Unable to enrich " + target, e);
66+
}
67+
})
68+
.orElse(target);
69+
}
70+
71+
default Client enrich(Client client) {
72+
return client;
73+
}
74+
75+
default Retryer enrich(Retryer retryer) {
76+
return retryer;
77+
}
78+
79+
default RequestInterceptor enrich(RequestInterceptor requestInterceptor) {
80+
return requestInterceptor;
81+
}
82+
83+
default Logger enrich(Logger logger) {
84+
return logger;
85+
}
86+
87+
default Level enrich(Level level) {
88+
return level;
89+
}
90+
91+
default Contract enrich(Contract contract) {
92+
return contract;
93+
}
94+
95+
default Options enrich(Options options) {
96+
return options;
97+
}
98+
99+
default Encoder enrich(Encoder encoder) {
100+
return encoder;
101+
}
102+
103+
default Decoder enrich(Decoder decoder) {
104+
return decoder;
105+
}
106+
107+
default InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
108+
return invocationHandlerFactory;
109+
}
110+
111+
default QueryMapEncoder enrich(QueryMapEncoder queryMapEncoder) {
112+
return queryMapEncoder;
113+
}
114+
115+
}

core/src/main/java/feign/Feign.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.lang.reflect.Type;
1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.stream.Collectors;
22+
import feign.Logger.Level;
2123
import feign.Logger.NoOpLogger;
2224
import feign.ReflectiveFeign.ParseHandlersByName;
2325
import feign.Request.Options;
@@ -113,6 +115,7 @@ public static class Builder {
113115
private boolean closeAfterDecode = true;
114116
private ExceptionPropagationPolicy propagationPolicy = NONE;
115117
private boolean forceDecoding = false;
118+
private List<Capability> capabilities = new ArrayList<>();
116119

117120
public Builder logLevel(Logger.Level logLevel) {
118121
this.logLevel = logLevel;
@@ -245,6 +248,11 @@ public Builder exceptionPropagationPolicy(ExceptionPropagationPolicy propagation
245248
return this;
246249
}
247250

251+
public Builder addCapability(Capability capability) {
252+
this.capabilities.add(capability);
253+
return this;
254+
}
255+
248256
/**
249257
* Internal - used to indicate that the decoder should be immediately called
250258
*/
@@ -262,6 +270,20 @@ public <T> T target(Target<T> target) {
262270
}
263271

264272
public Feign build() {
273+
Client client = Capability.enrich(this.client, capabilities);
274+
Retryer retryer = Capability.enrich(this.retryer, capabilities);
275+
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
276+
.map(ri -> Capability.enrich(ri, capabilities))
277+
.collect(Collectors.toList());
278+
Logger logger = Capability.enrich(this.logger, capabilities);
279+
Contract contract = Capability.enrich(this.contract, capabilities);
280+
Options options = Capability.enrich(this.options, capabilities);
281+
Encoder encoder = Capability.enrich(this.encoder, capabilities);
282+
Decoder decoder = Capability.enrich(this.decoder, capabilities);
283+
InvocationHandlerFactory invocationHandlerFactory =
284+
Capability.enrich(this.invocationHandlerFactory, capabilities);
285+
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
286+
265287
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
266288
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
267289
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright 2012-2020 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign;
15+
16+
import static org.hamcrest.CoreMatchers.nullValue;
17+
import static org.junit.Assert.assertThat;
18+
import org.hamcrest.CoreMatchers;
19+
import org.junit.Test;
20+
import java.io.IOException;
21+
import java.util.Arrays;
22+
import feign.Request.Options;
23+
24+
public class CapabilityTest {
25+
26+
private class AClient implements Client {
27+
28+
public AClient(Client client) {}
29+
30+
@Override
31+
public Response execute(Request request, Options options) throws IOException {
32+
return null;
33+
}
34+
35+
}
36+
private class BClient implements Client {
37+
38+
public BClient(Client client) {
39+
if (!(client instanceof AClient)) {
40+
throw new RuntimeException("Test is chaing invokations, expected AClient instace here");
41+
}
42+
}
43+
44+
@Override
45+
public Response execute(Request request, Options options) throws IOException {
46+
return null;
47+
}
48+
49+
}
50+
51+
@Test
52+
public void enrichClient() {
53+
Client enriched = Capability.enrich(new Client.Default(null, null), Arrays.asList(
54+
new Capability() {
55+
@Override
56+
public Client enrich(Client client) {
57+
return new AClient(client);
58+
}
59+
}, new Capability() {
60+
@Override
61+
public Client enrich(Client client) {
62+
return new BClient(client);
63+
}
64+
}));
65+
66+
assertThat(enriched, CoreMatchers.instanceOf(BClient.class));
67+
}
68+
69+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright 2012-2020 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.hystrix;
15+
16+
import com.netflix.hystrix.HystrixCommand;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
import feign.Capability;
20+
import feign.Contract;
21+
import feign.InvocationHandlerFactory;
22+
23+
/**
24+
* Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects. Also
25+
* decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()}
26+
* directly.
27+
*/
28+
public final class HystrixCapability implements Capability {
29+
30+
private SetterFactory setterFactory = new SetterFactory.Default();
31+
private final Map<Class, Object> fallbacks = new HashMap<>();
32+
33+
/**
34+
* Allows you to override hystrix properties such as thread pools and command keys.
35+
*/
36+
public HystrixCapability setterFactory(SetterFactory setterFactory) {
37+
this.setterFactory = setterFactory;
38+
return this;
39+
}
40+
41+
@Override
42+
public Contract enrich(Contract contract) {
43+
return new HystrixDelegatingContract(contract);
44+
}
45+
46+
@Override
47+
public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
48+
return (target, dispatch) -> new HystrixInvocationHandler(target, dispatch, setterFactory,
49+
fallbacks.containsKey(target.type())
50+
? new FallbackFactory.Default<>(fallbacks.get(target.type()))
51+
: null);
52+
}
53+
54+
public <E> Capability fallback(Class<E> api, E fallback) {
55+
fallbacks.put(api, fallback);
56+
57+
return this;
58+
}
59+
60+
}

0 commit comments

Comments
 (0)