深入了解feign(02)-源码解析
前言
通过上一篇文章深入了解feign(01)-使用入门的详情,我们理解了Feign如何使用。本节我们将深入Feign的源码,通过启动和运行两个阶段来分析源代码。
在进入正文之前,我们再来复习一下Feign的使用。
public interface UserService { @RequestLine("GET /user/get?id={id}") User get(@Param("id") Long id);}public class User { Long id; String name;}public class Main { public static void main(String[] args) { UserService userService = Feign.builder() .options(new Request.Options(1000, 3500)) .retryer(new Retryer.Default(5000, 5000, 3)) .target(UserService.class, "http://api.server.com"); System.out.println("user: " + userService.get(1L)); }}整个流程大概是这样的:先定义一个接口,而后在接口的类型公告、方法公告和方法参数上面定义少量注解,而后通过Feign.builder().{xx...}.target()方法生成一个接口代理商对象,最后对代理商对象实施方法调用。
整体结构图
分析之前,先给出整体结构图(该图片借用了Spring Cloud Feign设计原理),以便我们对Feign有一个整体的、清晰的认识。
Feign整体结构图
启动阶段
启动阶段在于Feign.Builder的调用。包括两个部分,第一部分使用了构建者模式+链式编程来填充对象属性,第二部分使用了target()方法来生成代理商对象。
1. Feign.Builder – 填充对象属性
public abstract class Feign { public static Builder builder() { return new Builder(); } public static class Builder { 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 ErrorDecoder errorDecoder = new ErrorDecoder.Default(); private Options options = new Options(); private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); private boolean decode404; public Builder logLevel(Logger.Level logLevel) { this.logLevel = logLevel; return this; } public Builder contract(Contract contract) { this.contract = contract; return this; } public Builder client(Client client) { this.client = client; return this; } public Builder options(Options options) { this.options = options; return this; } // 省略掉其余相似client()、options()的方法... }}2. Feign.Builder – target()方法
public static class Builder { public <T> T target(Class<T> apiType, String url) { return target(new HardCodedTarget<T>(apiType, url)); } public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }}- 为什么要有两个
target()方法呢?起因在于通过<T> T target(Target<T> target)方法,我们可以提供更灵活的定制功能 target()最终会调用new ReflectiveFeign(...)来生成Feign实例- 关键类
SynchronousMethodHandler.Factory用于创立一个SynchronousMethodHandler对象 - 关键类
ParseHandlersByName将Target的所有接口方法转换为Map<String, MethodHandler>对象 - 关键类
ReflectiveFeign是Feign的实现,Feign有一个模板方法public abstract <T> T newInstance(Target<T> target);用于生成代理商对象
3. ReflectiveFeign.newInstance()方法
@SuppressWarnings("unchecked")@Overridepublic <T> T newInstance(Target<T> target) { // 1. 根据target,解析生成`MethodHandler`对象 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // 2. 对`MethodHandler`对象进行分类整理,整理成两类:default方法和基本方法 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))); } } // 3. 通过jdk动态代理商生成代理商对象,这儿是最关键的地方 InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); // 4. 将`DefaultMethodHandler`绑定到代理商对象 for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy;}我们整理一下这个方法逻辑
- 根据target,解析生成
MethodHandler对象 - 对
MethodHandler对象进行分类整理,整理成两类:default方法和基本方法 - 通过jdk动态代理商生成代理商对象,这儿是最关键的地方
- 将
DefaultMethodHandler绑定到代理商对象
4. 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) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder); } else { buildTemplate = new BuildTemplateByResolvingArgs(md); } result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result;}- 该方法使用
Contract对象来解析接口和接口上的方法,而后生成接口方法元数据列表 - 遍历接口方法元数据列表,根据工厂
SynchronousMethodHandler.Factory生成MethodHandler对象列表 Contract可以有不同的实现,Feign自带了一套注解,实现类为Contract.Default。同时Spring Cloud对Spring MVC的注解(如@RequestMapping和@RequestParam等)也进行了封装,实现类为SpringMvcContractBaseContract.parseAndValidatateMetadata()方法全是java反射的应用,限于篇幅,这儿不再深入到细节
接下来我们看看SynchronousMethodHandler.Factory.create()方法的实现,非常的简单,就是调用了SynchronousMethodHandler的全参构造方法。
public MethodHandler create(Target<?> target, MethodMetadata md, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) { return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, decode404);}运行阶段
在启动阶段我们生成了接口的代理商对象,运行阶段其实就是调用该代理商对象的方法,来实现http远程调用。为了进一步加深我们队Feign的理解,我们这儿来分析一下代理商对象方法是如何调用的。
回顾一下代理商对象生成的代码。
public <T> T newInstance(Target<T> target) { InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);}static final class Default implements InvocationHandlerFactory { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new ReflectiveFeign.FeignInvocationHandler(target, dispatch); }}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); } @Override public boolean equals(Object obj) { if (obj instanceof FeignInvocationHandler) { FeignInvocationHandler other = (FeignInvocationHandler) obj; return target.equals(other.target); } return false; } @Override public int hashCode() { return target.hashCode(); } @Override public String toString() { return target.toString(); } }关键的地方在于FeignInvocationHandler.invoke()方法中的这一行代码:return dispatch.get(method).invoke(args);。
dispatch正是启动阶段生成的MethodHandler列表,我们进去看看它的invoke()方法。
final class SynchronousMethodHandler implements MethodHandler { @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) { retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }}那么这个方法做了哪些事情呢?
RequestTemplate对象的生成Retryer的重试机制executeAndDecode(template)方法
1. RequestTemplate对象的生成
该方法首先生成了RequestTemplate对象,该对象是通过BuildTemplateFromArgs来创立的,而BuildTemplateFromArgs的创立过程在于ParseHandlersByName.apply()方法,我们回过头再来看看这个方法。这个方法会根据参数的形式来生成不同的类对象,他们解决参数逻辑会不一样。
- 假如有表单参数,则生成
BuildFormEncodedTemplateFromArgs类型对象 - 假如带body,则生成
BuildEncodedTemplateFromArgs类型对象 - 其余情况,生成
BuildTemplateByResolvingArgs类型对象
static final class ParseHandlersByName { 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) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder); } else { buildTemplate = new BuildTemplateByResolvingArgs(md); } result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; }}2. Retryer的重试机制
Retryer不是线程安全的对象,所以每一次方法调用我们都需要借助于原型模式来生成一个新的对象。Retryer关键的模板方法如下
注释说得相当清楚!假如重试允许的话,直接返回(可能在休眠肯定时间之后)。其余情况,需要对外抛出异常对请求进行终止。
/** * if retry is permitted, return (possibly after sleeping). Otherwise propagate the exception. */void continueOrPropagate(RetryableException e);我们回头来看看invoke()方法。在执行的过程中要想重试,需要抛出特定的异常RetryableException,否则重试将不生效。重试的时候,假如Feign设置的日志级别比NONE更高,将记录重试日志。限于篇幅,本文不开展讲述Retryer的实现。
@Overridepublic 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) { retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } }}3. executeAndDecode(template)方法
终于进入到了调用client关键的一环了,真是很不容易!我们进一步来看看这个方法,该方法比较长,前方高能!
Object executeAndDecode(RequestTemplate template) throws Throwable { // 1. 根据`RequestTemplate`生成`Request`对象 Request request = targetRequest(template); // 2. 记录请求日志 if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { // 3. 调用`client`对象的`execute()`方法执行http调用逻辑。`execute()`内部可可设置request对象,也可能不设置,所以需要`response.toBuilder().request(request).build();`这一行代码 response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } catch (IOException e) { // 4. IOException的时候,记录日志 if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } // 5. IOException的时候,包装成`RetryableException`异常 throw errorExecuting(request, e); } // 6. 统计`client.execute()`花费的时间,并后续日志记录 long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) { response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } // 7. 假如元数据返回类型是`Response`,直接返回回去就可,不需要`decode()`解码 if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } // 8. 将`response`解码返回,主要对`2xx`和`404`等进行解码,`404`需要特别的开关控制。其余情况,使用`errorDecoder`进行解码,以异常的方式返回 if (response.status() >= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null; } else { return decode(response); } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { return decode(response); } else { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { // 9. 在对`Response`使用完成之后,需要关闭`Response`,由于`Response`可能有对输入流的操作 if (shouldClose) { ensureClosed(response.body()); } }}该方法做的事情,我们罗列一下:
- 根据
RequestTemplate生成Request对象 - 记录请求日志
- 调用
client对象的execute()方法执行http调用逻辑。execute()内部可可设置request对象,也可能不设置,所以需要response.toBuilder().request(request).build();这一行代码 - IOException的时候,记录日志
- IOException的时候,包装成
RetryableException异常 - 统计
client.execute()花费的时间,并后续日志记录 - 假如元数据返回类型是
Response,直接返回回去就可,不需要decode()解码 - 将
response解码返回,主要对2xx和404等进行解码,404需要特别的开关控制。其余情况,使用errorDecoder进行解码,以异常的方式返回 - 在对
Response使用完成之后,需要关闭Response,由于Response可能有对输入流的操作
我们接下来分析一下每个点涉及的逻辑。先来看看targetRequest(),这是先调用的阻拦器方法,再生成Request对象。需要注意的是,这儿的阻拦器并没有像tomcat那样使用过滤器模式来实现,而仅仅是简单的for循环。
Request targetRequest(RequestTemplate template) { for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(new RequestTemplate(template));}feign.Client.Default.execute()方法使用了HttpURLConnection的方式来请求web服务器,并没有使用对象池技术,所以性能较低。如何使用HttpURLConnection和web服务器进行通信的底层细节并不是我们需要分析的重点,在此仅一笔带过。
接下来我们比较关心的是decode()方法,该方法将输入流转换为我们需要的POJO业务对象。也是非常简单,只是调用传入的Decoder.decode()方法。
Object decode(Response response) throws Throwable { try { return decoder.decode(response, metadata.returnType()); } catch (FeignException e) { throw e; } catch (RuntimeException e) { throw new DecodeException(e.getMessage(), e); }}至此,我们完整地分析完了executeAndDecode()方法。
总结
我们先给出Feign的整体结构图,使得我们有一个总的认识。而后通过Feign的启动和运行两个阶段的源码阅读和分析,已经对Feign有了比较深入的理解。
其实说究竟,Feign有点相似于http用户端的门面(Facade)。我们可以扩展实现编码器、解码器、Client等组件。对于使用者提供了基于注解的规范,我们可以使用各种自己想要的注解来定制我们的调用方式。将我们从复杂的、多变的和细节的底层http用户端类库中解放出来。
笔者认为,站在笼统的角度来说,Feign和Slf4j是同一类东西,他们都可以看成是一套规范,或者者是一套模式。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 深入了解feign(02)-源码解析