OpenFeign源码学习

得意时要看淡,失意时要看开。不论得意失意,切莫大意;不论成功失败,切莫止步。志得意满时,需要的是淡然,给自己留一条退路;失意落魄时,需要的是泰然,给自己觅一条出路OpenFeign源码学习,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、在SpringBoot中使用OpenFeign

1、在 pom.xml 文件中添加 openfeign 的 starter

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

2、在启动类上加上 @EnableFeignClients 注解

@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
	public static void main(String[] args) {
    	SpringApplication.run(OrderApplication.class, args)
  	}
}

3、在接口上增加 @FeignClient 注解

@FeignClient(name = "user-service", url = "http://127.0.0.1:8001/user-service")
public interface IUserFeign {

    @GetMapping("/user/queryUserName")
    String queryUserName(@RequestParam("userId") String userId);
}

4、最后在使用的地方,注入这个接口,调用方法即可

@Service
public class OrderServiceImpl implements IOrderService {

	@Resource
    IUserFeign userFeign;

    @Override
    public List<Order> list() {
        //...省略
        String userName = userFeign.queryUserName(userId);
        //...省略
    }

}

二、SpringBoot启动时,OpenFeign做了哪些事

入口从 @EnableFeignClients 注解开始

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	//...省略
}

@EnableFeignClients 注解通过 @Import(FeignClientsRegistrar.class) 导入了扫描注册类 FeignClientsRegistrar,而 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口并重写 registerBeanDefinitions() 方法,我们看下这个方法

//FeignClientsRegistrar
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    registerDefaultConfiguration(metadata, registry);
    registerFeignClients(metadata, registry);
}

重点看 registerFeignClients 方法

//FeignClientsRegistrar
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
            FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
            : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            basePackages.add(ClassUtils.getPackageName(clazz));
            clientClasses.add(clazz.getCanonicalName());
        }
        AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
            }
        };
        scanner.addIncludeFilter(
                new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }

    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
                .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition 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);
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));

                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

这个方法会扫描当前应用程序上下文中所有标记有 @FeignClient 注解的接口,然后调用 registerFeignClient 方法,我们继续看56行 registerFeignClient

//FeignClientsRegistrar
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = getContextId(attributes);
    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);

    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
                                                            // null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
            new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

这个方法是把 bean 定义注册到 BeanFactory 里,重点是第4行,设置 BeanClass 类型为 FeignClientFactoryBean,又是熟悉的 FactoryBean,这些打上 @FeignClient 注解的接口,在实例化的时候,其实调用的是 FeignClientFactoryBeangetObject 方法,我们看下这个方法

//FeignClientFactoryBean
@Override
public Object getObject() throws Exception {
    return getTarget();
}

<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    //这里包含了Feign的配置信息,有兴趣的可以看看,比如超时时间,重试次数之类的
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        return (T) loadBalance(builder, context,
                new HardCodedTarget<>(this.type, this.name, this.url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
            new HardCodedTarget<>(this.type, this.name, url));
}

继续看37行,targeter.target,因为 spring-cloud-starter-openfeign 默认会集成 hystrix,所以 FeignAutoConfiguration 里的 Targeter 会实例化 HystrixTargeter
图片.png
所以这里的 targeter.target 会进到 HystrixTargetertarget 方法

//HystrixTargeter
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
        FeignContext context, Target.HardCodedTarget<T> target) {
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        return feign.target(target);
    }
    feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    SetterFactory setterFactory = getOptional(factory.getName(), context,
            SetterFactory.class);
    if (setterFactory != null) {
        builder.setterFactory(setterFactory);
    }
    Class<?> fallback = factory.getFallback();
    if (fallback != void.class) {
        return targetWithFallback(factory.getName(), context, target, builder,
                fallback);
    }
    Class<?> fallbackFactory = factory.getFallbackFactory();
    if (fallbackFactory != void.class) {
        return targetWithFallbackFactory(factory.getName(), context, target, builder,
                fallbackFactory);
    }

    return feign.target(target);
}

这里如果配置文件里启用了 hystrix,那么方法参数里的 feign 就会是 feign.hystrix.HystrixFeign.Builder 类型,就会跳过第5行的 if,继续往下走,其实就是多了些 fallback 的逻辑,不管怎样最终都会走到 Feigntarget 方法里

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

继续,来到 ReflectiveFeignnewInstance 方法

//ReflectiveFeign
@Override
public <T> T newInstance(Target<T> target) {
    Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap<Method, InvocationHandlerFactory.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;
}

首先第4行的方法返回的 nameToHandler 里的 MethodHandler 的类型是 SynchronousMethodHandler,这里就不展开了,然后 methodToHandler 是把 method 对应的 MethodHandler 缓存起来,缓存的就是这个 SynchronousMethodHandler,然后我们看到第20行熟悉的代理对象创建方法,我们知道重点是第3个参数 InvocationHandler,因为最终执行的就是这个 InvocationHandlerinvoke 方法,我们继续看19行,这个 InvocationHandler 是怎么创建的,注意这里把装有 SynchronousMethodHandler 的 map 作为入参传了进去
如果没有启用 hystrix,来到的是 InvocationHandlerFactory 内部类 Default 里的 create 方法

//InvocationHandlerFactory -> Default
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
  return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}

可以看到最终创建的是 FeignInvocationHandler 对象,而装有 SynchronousMethodHandler 的map正是其成员变量

三、OpenFeign是如何调用远程接口的

前面我们知道,SpringBoot 启动时,会为包含注解 @FeignClient 的接口创建代理对象,而调用这些接口的方法,最终会调用 FeignInvocationHandlerinvoke 方法,我们看下

//FeignInvocationHandler
@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);
}

可以看到,最终会从装有 SynchronousMethodHandler 的map中取出 SynchronousMethodHandler,然后执行其 invoke 方法,我们看下 SynchronousMethodHandlerinvoke 方法

//SynchronousMethodHandler
@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;
        }
    }
}

继续第8行 executeAndDecode 方法

//SynchronousMethodHandler
Object executeAndDecode(RequestTemplate template) 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);
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        throw errorExecuting(request, e);
    }
    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);
        }
        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();
        }
        if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
                return null;
            } else {
                Object result = decode(response);
                shouldClose = closeAfterDecode;
                return result;
            }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            Object result = decode(response);
            shouldClose = closeAfterDecode;
            return result;
        } 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 {
        if (shouldClose) {
            ensureClosed(response.body());
        }
    }
}

已经很明显了,跟进11行 response = client.execute(request, options);

//Client
@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection, request);
}

可以看到 convertAndSend 里就是最终发送http请求的代码

四、总结

1、在 SpringBoot 项目启动阶段,扫描所有 @FeignClient 注解标注的接口,为其创建代理对象,把接口中的方法交由 FeignInvocationHandler 处理。
2、当客户端调用该代理对象上的方法时,FeignInvocationHandler 会将方法调用转化为 http 请求,发送给服务端。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154473.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!