> 文章列表 > 再学一下Feign的原理

再学一下Feign的原理

再学一下Feign的原理

简介

Feign是Spring Cloud Netflix组件中的一个轻量级Restful的HTTP服务客户端,它简化了服务间调用的方式。
Feign是一个声明式的web service客户端.它的出现使开发web service客户端变得更简单.使用Feign只需要创建一个接口加上对应的注解, 比如@FeignClient注解。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Fegin,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样,开发者完全无感知,更感知不到HTTP请求。
Feign的特性:

  • 可插拔的注解支持,包含Feign注解
  • 支持可插拔的HTTP编码器和解码器
  • 支持Hystrix和它的Fallback
  • 支持Ribbion的负载均衡
  • 支持HTTP请求和响应的压缩

快速入门

引入jar

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

编码

@FeignClient(url = "/test",name = "test-api"
)
public interface TestClient {@PostMapping(value = {"/create"})void create(@RequestBody User user);}

启动注解扫描

@Configuration
@EnableFeignClients(basePackages = {"com.test"})

添加注解,启动FeignClient注解扫描,跟我们平时使用ComponentScan的道理都是相似的。

使用

    @Resourceprivate TestClient testClient;public void create() {User user = new User();user.setName("test");testClient.create(user);}

完成这些操作之后,我们就可以正常的调用其他服务了,是不是很简单,很容易上手?
那么它到底是如何实现的呢?它都帮我们做了什么事情?接下来的内容才是我们需要了解和掌握的内容。

注册

在我们的业务代码中,我们只是定义了一个添加@FeignClient注解的接口,它是通过JDK的动态代理帮我们创建了接口的代理类,通过代理类完成HTTP的远程调用操作。
在上面的demo中,我们说了,想要调用其他服务,必须添加@EnableFeignClients注解,这个注解就是要扫描我们的代码所有标注了@FeignClient的接口,然后添加到容器当中。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<?>[] defaultConfiguration() default {};Class<?>[] clients() default {};}

我们可以自己定义我们要扫描的包路径,加载所有的@FeignClient标识的所有接口。
在这个注解的上面还有一个注解,@Import,这个我想大家应该都知道,是为了导入其他的bean到容器当中,那么Feign的主要加载、扫描、添加注册信息等操作,都是在FeignClientsRegistrar中完成的。

FeignClientsRegistrar

spring框架已经成为了我们日常开发的企业级框架,或者说已经离不开spring的支持了。
FeignClientsRegistrar的处理逻辑就是在调用ApplicationContext的refresh方法的时候,需要初始化一些BeanPostProcessor,在这个过程中,需要处理一些配置类,比如添加了@Configuration注解的类,那么我们使用的Feign开启的时候,就需要这个配置注解。
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在容器启动时候就会调用它的registerBeanDefinitions方法,做一些前置处理,通常是把一些bean定义信息注册到容器当中,便于后面初始化这个bean。
现在我们主要看一下FeignClientsRegistrar的registerBeanDefinitions方法

registerFeignClients

public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Set<String> basePackages;Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);。。。省略 //主要是为了获取扫描的包路径,也就是basePackagesfor (String basePackage : basePackages) {//查找添加了@FeignClient注解的接口Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);//添加FeignClientSpecification的bean定义信息registerClientConfiguration(registry, name,attributes.get("configuration"));//添加FeignClientFactoryBean的bean定义信息//注册的是FactoryBean信息,那么在初始化这个bean的时候,就会调用到getObject方法,代理的逻辑也是在这里面处理的registerFeignClient(registry, annotationMetadata, attributes);}}}}

registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();//feignclient注解的bean是FeignClientFactoryBean,一个FactoryBeanBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);//设置url路径definition.addPropertyValue("url", getUrl(attributes));//urlpathdefinition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);//设置服务名definition.addPropertyValue("name", name);String contextId = getContextId(attributes);//beanname的替代名definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));//设置失败机制definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);//设置bean的别名String alias = contextId + "FeignClient";//注解bean定义信息BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

到这里呢, 所有的添加@FeignClient注解的接口已经把bean信息注册到了容器中,等待的就是bean的初始化动作.

bean初始化

在bean的初始化中,由于bean之间的相互依赖,一定会调用FeignClientFactoryBean的getObject方法.
再学一下Feign的原理
继续跟踪创建代理类的代码

Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));

type就是我们定义的Feign接口, name和url就是注解中定义的属性.
接着就会调用到FeignBuilder的target方法

public <T> T target(Target<T> target) {return build().newInstance(target);}

调用build方法封装ReflectiveFeign数据,ReflectiveFeign继承了Feign,后面我们调用FeignClient的时候,就是调用它的内部类FeignInvocationHandler的invoke方法

public Feign build() {Client client = Capability.enrich(this.client, capabilities);Retryer retryer = Capability.enrich(this.retryer, capabilities);//Feign调用链路的拦截器List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream().map(ri -> Capability.enrich(ri, capabilities)).collect(Collectors.toList());//... 省略//设置SynchronousMethodHandler的内部工厂数据SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);//创建ReflectiveFeignreturn new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}

接着调用ReflectiveFeign的newInstance方法,Feign接口的代理类的生成就是在这里产生的,每个方法对应的MethodHandler也是在这里注册好的.

public <T> T newInstance(Target<T> target) {//这里就会遍历元数据信息,生成每个方法名对应的MethodHandler,这里的MethodHandler就是通过SynchronousMethodHandler.Factory//创建的SynchronousMethodHandler//MethodHandler是Fegin定义个的方法处理器Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);//method -> MethodHandlerMap<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();//遍历Feign接口中定义的所有方法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)));}}//一个Feign接口创建一个对应的InvocationHandlerInvocationHandler handler = factory.create(target, methodToHandler);//通过jdk动态代理创建代理对象T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);//为默认的方法处理器绑定创建的代理对象for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}

InvocationHandler 这里返回的对象还是ReflectiveFeign的内部类FeignInvocationHandler

  static class FeignInvocationHandler implements InvocationHandler {private final Target target;private final Map<Method, MethodHandler> dispatch;

首先它实现了jdk代理的InvocationHandler 接口,包含2个成员变量,一个目标类, 一个是解析后的方法到MethodHandler的映射关系,便于后面调用的时候,通过dispatch查询对应方法的MethodHandler.
到这里Feign接口的相关注册和初始化动作就结束了.

消费

在这里插入图片描述
懒的画图了,从网上复制了一份, 基本上可以完整的概括了FeignClient的调用流程了.

FeignInvocationHandler

了解jdk动态代理的都知道, 想要实现动态代理,首先要实现InvocationHandler接口, 调用的时候就会执行invoke方法,接下来我们仔细看下invoke方法的执行流程.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//... 省略//通过初始化好的dispatch,根据对应调用的method查找对应的methodHandler, 从上述的分析过程中,我们看到使用的是//SynchronousMethodHandlerreturn dispatch.get(method).invoke(args);}

SynchronousMethodHandler

追踪到对应的handler上看下

public Object invoke(Object[] argv) throws Throwable {//封装RequestTemplate 对象, 它包含http相关请求的url,body,method,等等一下参数RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);//失败重试策略Retryer retryer = this.retryer.clone();while (true) {try {//执行服务调用,并且解析返回的数据return executeAndDecode(template, options);} catch (RetryableException e) {try {retryer.continueOrPropagate(e);} catch (RetryableException th) {//...}//....continue;}}}

接下来的就是使用Http客户端调用远程服务

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {response = client.execute(request, options);// ensure the request is set. TODO: remove in Feign 12response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}//...}

http客户端也有很多的实现
再学一下Feign的原理
再学一下Feign的原理
其中Okhttp和httpclient是自动装配的, 我们只需要配置一下,就可以启用对应的http客户端,缺省配置的情况下,默认使用ApacheHttpClient.

OkHttp

public feign.Response execute(feign.Request input, feign.Request.Options options)throws IOException {okhttp3.OkHttpClient requestScoped;if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()|| delegate.readTimeoutMillis() != options.readTimeoutMillis()|| delegate.followRedirects() != options.isFollowRedirects()) {requestScoped = delegate.newBuilder().connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();} else {requestScoped = delegate;}Request request = toOkHttpRequest(input);Response response = requestScoped.newCall(request).execute();return toFeignResponse(response, input).toBuilder().request(input).build();}

这里就是OkHttp的远程服务调用代码, 没有太多的逻辑.

重试

重试默认使用的Retryer.Default
我们先看下它的默认参数

public Default() {this(100, SECONDS.toMillis(1), 5);}

最大尝试次数是5次, 最大时间间隔是1s, 100ms的重试时间间隔, 每次的interval是动态计算下次尝试需要等待的时间

public void continueOrPropagate(RetryableException e) {//超过最大重试次数直接抛出异常if (attempt++ >= maxAttempts) {throw e;}long interval;if (e.retryAfter() != null) {interval = e.retryAfter().getTime() - currentTimeMillis();if (interval > maxPeriod) {interval = maxPeriod;}if (interval < 0) {return;}} else {//根据时间间隔和重试次数计算下次重试需要等待的时间interval = nextMaxInterval();}try {Thread.sleep(interval);} catch (InterruptedException ignored) {Thread.currentThread().interrupt();throw e;}sleptForMillis += interval;}

Feign的初始化和调用基本原来,先分享到这里,下一期分享一下和ribbon的结合,实现负载均衡.

网赚资料