文章概览
- clone feign的x.x.x版本分支进行一个DEMO尝试
- 简单分析feign的构造原理
feign入门
- feign是一个声明式的http客户端工具,帮助开发者更好,更优雅地去处理http的请求和响应
- 版本说明
- spring cloud starter openfeign [2.1.3.RELEASE]版本依赖于feign的10.2.3版本,所以接下来说有的内容都是基于10.2.3版本的feign
feign的项目构成
-- benchmark //性能测试用例
-- core //feign的核心包
-- example-xx * 2 //示例包
// 下面均为对一些第三方模块的集成
-- gson
-- httpclient
-- hystrix
-- ....
feign -hello world
- 利用feign完成一次http请求(以下代码来自 feign-core/src/main/java/test/BaseApiTest.java)
public class BaseApiTest {
@Rule
public final MockWebServer server = new MockWebServer();
interface BaseApi<K, M> {
@RequestLine("GET /api/{key}")
Entity<K, M> get(@Param("key") K key);
}
static class Entity<K, M> {
K key;
M model;
}
interface MyApi extends BaseApi<String, Long> {
}
@Test
public void resolvesParameterizedResult() throws InterruptedException {
server.enqueue(new MockResponse().setBody("foo"));
String baseUrl = server.url("/default").toString();
Feign.builder()
.decoder(new Decoder() {
@Override
public Object decode(Response response, Type type) {
assertThat(type)
.isEqualTo(new TypeToken<Entity<String, Long>>() {}.getType());
return null;
}
})
.target(MyApi.class, baseUrl).get("foo");
assertThat(server.takeRequest()).hasPath("/default/api/foo");
}
}
- 如何快速入门feign的使用
- 通过feign-core下的test用例,可以完成feign大多数功能特性的使用
feign的构造原理
先说总结
构建一个最简单的feign客户端
public class MyTest {
interface Api {
@RequestLine("GET /")
String get();
}
@Test
public void test() {
Api target = Feign.builder().target(Api.class, "https://www.baidu.com");
String resp = target.get();
System.out.println(resp);
}
}
Feign.builder()
- builder()方法实际上是返回了一个feign.Feign.Builder
- Builder有什么东西?以上述demo为例子,我只传入target属性,那么其他应该为默认
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
- T target(Class apiType, String url)
- 调用这个方法后,就返回一个代理类了,是一个真正可执行的http客户端类
- 简述 feign.Target
public interface Target<T> {
Class<T> type();
String name();
String url();
public Request apply(RequestTemplate input)
}
- feign提供了2个默认实现
- feign.Target.HardCodedTarget
- apply方法会对最终url进行协议修正(feign.Target.HardCodedTarget#apply)
- feign.Target.EmptyTarget
- build().newInstance(target); // 这是target的实现,这里要分两步来看
- feign.Feign.Builder#build
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
- feign.ReflectiveFeign.ParseHandlersByName#apply
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
- feign.Feign#newInstance(这个方法可以说是了解feign运行机制的最重要的方法了)
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
- feign.ReflectiveFeign.FeignInvocationHandler#invoke
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
执行方法真正逻辑
// 这个过程我们在上面进行了分析
Api target = Feign.builder().target(Api.class, "https://www.baidu.com")
// 这里就是真正逻辑的执行,那么get方法就会进入到FeignInvocationHandler 的invoke
// invoke会从dispatch选取到合适的methodHandler进行invoke
String resp = target.get()
- feign.SynchronousMethodHandler#invoke
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}