OpenFeign源码

OpenFeign其实还是负责通信的,在使用过程中,让开发者可以面向接口开发的方式进行远程调用,那么:

  1. OpenFeign的注解是如何被扫描解析的呢?
  2. 注解标注的接口注入到IoC中的实例是什么?
  3. OpenFeign的上下文是如何构建的呢?
  4. 远程通信是如何实现的呢?与RestTemplate有关系么?

带着以上的问题,准备开始Feign的源码,同样的 在Spring的生态下,切入点还是那几个,EnableFeignClientsFeignAutoConfiguration,其实 如果看一眼spring-cloud-openfeign-core里面的spring.factories就发现 这里面其实有好多AutoConfiguration。

OpenFeign初始化

@EnableFeignClients

这个注解

/**
 * Scans for interfaces that declare they are feign clients (via
 * {@link org.springframework.cloud.openfeign.FeignClient} @FeignClient).
 * Configures component scanning directives for use with
 * {@link org.springframework.context.annotation.Configuration}
 * @Configuration classes.
 * 扫描所有标注了@FeignClient的接口 将其配置成配置类
 */
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    ....
}

这时候 关注点就应该在FeignClientsRegistrar

FeignClientsRegistrar

虽然没有在任何的配置中看到 实例化它,但是它实例化的还是很早的,在 org.springframework.context.annotation.ConfigurationClassParser#processImports

registerBeanDefinitions

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    ....
    // 重点关注这里 ImportBeanDefinitionRegistrar#registerBeanDefinitions
    // 两个参数分别是StandardAnnotationMetadata, DefaultListableBeanFactory
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
                                        BeanDefinitionRegistry registry) {
        
        // 如果注解中的defaultConfiguration不为空 则将其注入到IoC容器
        registerDefaultConfiguration(metadata, registry);
        // 这里是注入FeignClient的地方
        registerFeignClients(metadata, registry);
    }
    ....
}

registerFeignClients

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    ....
    public void registerFeignClients(AnnotationMetadata metadata,
                                     BeanDefinitionRegistry registry) {

        LinkedHashSet candidateComponents = new LinkedHashSet<>();
        // 获取 @EnableFeignClients 所有的配置信息
        Map attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        // 获取 @EnableFeignClients.clients 的值
        final Class[] clients = attrs == null ? null
                : (Class[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 这个集合必然不为空 分别取了value、basePackages、basePackageClasses
            // 如果那三个为空 则将标注了@EnableFeignClients 的类所在的路径加入到这个集合
            Set basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        } else {
            for (Class clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }
        
        // 遍历 并生成代理类 并注入到IoC容器
        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 attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                // @FeignClient的value值
                String name = getClientName(attributes);
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注入标注了@FeignClient的接口
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
    ....
    
}

registerFeignClient

这里面没啥可说的 就是将被FeignClient标注的接口注入到IoC容器 注入的是 FeignClientFactoryBean

目前这个阶段是初始化,实例化的时候会调用FeignClientFactoryBean.getObject()

class FeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
        
    ....
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        // 校验了 fallback和fallbackFactory
        validate(attributes);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        // FeignClient并无contextId的配置 这里取的还是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();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

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

        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);
    }
    ....
}

FeignClientFactoryBean#getTarget

首先要清楚FeignClientFactoryBean的实例里面在上面的步骤已经将它里面的值做了初始化。

而在doGetBean时 会调用getObject方法 ,这里如果不清楚,可以看案例关于FactoryBean

其次就是理解getTarget做了哪些事情:

  1. 生成远程代理
  2. 解析接口模板,例如@GetMapping等
  3. 处理负载均衡 集成Ribbon
  4. 包装熔断 继承Hystrix
 T getTarget() {
    // 这个就是feign的上下文 与spring 容器的上下文隔离开了
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // 构建一个Feign的Builder
    Feign.Builder builder = feign(context);

    // 这里判断的是 @FeignClient 的url是否做了配置
    if (!StringUtils.hasText(url)) {
        if (!name.startsWith("http")) {
            url = "http://" + name;
        }
        else {
            url = name;
        }
        url += cleanPath();
        // 这里做负载均衡
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(type, name, url));
    }
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
        url = "http://" + 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();
        }
        if (client instanceof FeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(type, name, url));
}

FeignClientFactoryBean#feign

只是构建了Feign.Builder,其实就是将一些参数记录。

protected Feign.Builder feign(FeignContext context) {
    
    // get方法 = context.getInstance(contextId, type)
    // 其中 context就是第一个参数 type是第二个参数 
    // contextId是FeignClientsRegistrar.registerFeignClient方法里面设置的
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    // 记录日志的 this.logger != null ? this.logger : new Slf4jLogger(type)
    Logger logger = loggerFactory.create(type);

    // @formatter:off
    Feign.Builder builder = get(context, Feign.Builder.class)
        // required values
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
    // @formatter:on

    // 配置相关的内容 logger encoder decoder...
    configureFeign(context, builder);

    return builder;
}

FeignClientFactoryBean#loadBalance

名字虽然叫loadBalance,但是这里并没有做负载均衡,第一行代码 Client client = getOptional(context, Client.class);,是从IoC容器中获取Client的Bean,这时触发了IoC容器对该Bean的实例化,代码如下:

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }
}
protected  T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget target) {
    // client = org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
    Client client = getOptional(context, Client.class);
    if (client != null) {
        // 这里构造的client
        builder.client(client);
        // targeter = org.springframework.cloud.openfeign.HystrixTargeter
        // 即使在项目中没有引入hystrix 也是这个类
        Targeter targeter = get(context, Targeter.class);
        // 这里面做动态代理的构建 与 模板方法的解析
        return targeter.target(this, builder, context, target);
    }

    throw new IllegalStateException(
        "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

HystrixTargeter#target

public  T target(FeignClientFactoryBean factory, Feign.Builder feign,
                    FeignContext context, Target.HardCodedTarget target) {
    // 没有引入hystrix 时 feign = Feign$Builder 所以就走到了这里
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        // 这里等于 build().newInstance(target),build构建了`ReflectiveFeign`
        // 然后这个newInstance是feign.ReflectiveFeign#newInstance
        return feign.target(target);
    }
    feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
        : factory.getContextId();
    SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
    if (setterFactory != null) {
        builder.setterFactory(setterFactory);
    }
    Class fallback = factory.getFallback();
    if (fallback != void.class) {
        return targetWithFallback(name, context, target, builder, fallback);
    }
    Class fallbackFactory = factory.getFallbackFactory();
    if (fallbackFactory != void.class) {
        return targetWithFallbackFactory(name, context, target, builder,
                                         fallbackFactory);
    }

    return feign.target(target);
}

这里的代码跳转是这样的,return feign.target(target); 点进去是feign.Feign.Builder#target(feign.Target),而方法的内容只有return build().newInstance(target);,而build方法 除了实例化了几个类,同时将Builder的一些参数做了传递,

这里做了一件事就是把client传过去了。

最后返回了ReflectiveFeign,所以直接看feign.ReflectiveFeign#newInstance

ReflectiveFeign#newInstance

这里就是通过JDK的动态代理 代理所有的接口了

public  T newInstance(Target target) {

    // value 是 feign.SynchronousMethodHandler 这一步是模板解析
    // 这一步初始化了 SynchronousMethodHandler里面的client
    Map nameToHandler = targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();

    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)));
        }
    }
    // create方法等于new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    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;
}

OpenFeign远程调用

从初始化的流程来看 feign.MethodHandlerfeign.SynchronousMethodHandler,而java.lang.reflect.InvocationHandlerfeign.ReflectiveFeign.FeignInvocationHandler,然后在调用方法时 首先要进入的就是feign.ReflectiveFeign.FeignInvocationHandler#invoke

ReflectiveFeign.FeignInvocationHandler#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 这里时过滤掉Object相关的方法
    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();
    }
    // dispatch是Map (value是SynchronousMethodHandler)
    return dispatch.get(method).invoke(args);
}

SynchronousMethodHandler#executeAndDecode

调用链为:

feign.SynchronousMethodHandler#invokefeign.SynchronousMethodHandler#executeAndDecode

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 {
        // 重点在这里 client=org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
        // 在不做特殊配置的情况下,LoadBalancerFeignClient.delegate=feign.Client$Default
        // 用的还是HttpURLConnection。
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 12
        response = 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);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
        return decoder.decode(response, metadata.returnType());

    CompletableFuture resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
                                        metadata.returnType(),
                                        elapsedTime);

    try {
        if (!resultFuture.isDone())
            throw new IllegalStateException("Response handling not done");

        return resultFuture.join();
    } catch (CompletionException e) {
        Throwable cause = e.getCause();
        if (cause != null)
            throw cause;
        throw e;
    }
}

LoadBalancerFeignClient#execute

到这里能看到一些负载均衡的字眼了 但是追进去会看到好多RxJava的代码,这种反应式代码 只能靠猜。

@Override
public Response execute(Request request, Request.Options options) throws IOException {
    try {
        URI asUri = URI.create(request.url());
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);

        IClientConfig requestConfig = getClientConfig(options, clientName);
        // FeignLoadBalancer.executeWithLoadBalancer 其实是父类AbstractLoadBalancerAwareClient
        return lbClient(clientName)
            .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
            throw io;
        }
        throw new RuntimeException(e);
    }
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(new ServerOperation() {
            @Override
            public Observable call(Server server) {
                URI finalUri = reconstructURIWithServer(server, request.getUri());
                S requestForServer = (S) request.replaceUri(finalUri);
                try {
                    // 最后回调到这里
                    // org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
                    return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                } 
                catch (Exception e) {
                    return Observable.error(e);
                }
            }
        }).toBlocking()
        .single(); // 执行之后可以走到com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
    } catch (Exception e) {
        Throwable t = e.getCause();
        if (t instanceof ClientException) {
            throw (ClientException) t;
        } else {
            throw new ClientException(e);
        }
    }

}

总结

这里针对开篇提出的问题做一个总结

  1. OpenFeign的注解是如何被扫描解析的呢?
  2. 注解标注的接口注入到IoC中的实例是什么?
  3. OpenFeign的上下文是如何构建的呢?
  4. 远程通信是如何实现的呢?与RestTemplate有关系么?

OpenFeign的注解是如何被扫描解析的呢?

通过实现接口ImportBeanDefinitionRegistrar然后注入实例到IoC容器的。

注解标注的接口注入到IoC中的实例是什么?

就是一个JDK的动态代理java.lang.reflect.Proxy,其中java.lang.reflect.Proxy#h的值是ReflectiveFeign$FeignInvocationHandler,如下图所示:

OpenFeign的上下文是如何构建的呢?

public class FeignContext extends NamedContextFactory

远程通信是如何实现的呢?与RestTemplate有关系么?

其实就是这个类org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient,这里用到了委派模式,delegate再默认情况下是feign.Client$Default,里面的通信方式是java.net.HttpURLConnection,可以切换到HttpClient或者OkHttp3

RestTemplate默认也是基于java.net.HttpURLConnection封装的一个工具类,Feign中没有直接使用RestTemplate,他们应该属于平级关系,不存在谁使用了谁。

疑问点

如何整合的Ribbon?

LoadBalancerFeignClient

和单独看Ribbon的源码还有联系么?

有关系 但是不太一样了,首先说明一点,Feign 一定要引入spring-cloud-starter-netflix-ribbon,否则在org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance就会报错。因此,其实分析的Ribbon的源码过程依旧会走一遍,且Feign比Ribbon 早配置。

为啥说不太一样了呢?因为用了Feign就不需要@LoadBalanceRestTemplate了,所以请求的时候不会再被LoadBalancerInterceptor拦截。后面负载均衡那里还会走到Ribbon的com.netflix.loadbalancer.BaseLoadBalancer#chooseServer

如何切换请求方式

org.springframework.cloud.openfeign.ribbon.OkHttpFeignLoadBalancedConfiguration

org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration

org.springframework.cloud.openfeign.loadbalancer.DefaultFeignLoadBalancerConfiguration

Spring的知识点

ImportBeanDefinitionRegistrar

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                        BeanDefinitionRegistry registry) {

        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(Hello.class.getName());

        registry.registerBeanDefinition("hello", beanDefinition);
    }

}

class Hello {
    // 省略 getter/setter/toString()
    private String name;
}
// 非Spring Boot的环境下,此注解必须有  不然IoC容器中无Hello
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);
        System.out.println(context.getBean(Hello.class));
    }
}

关于FactoryBean

本案例参考FeignClientFactoryBean,将一个接口做出一个代理对象 同时注入到Spring IoC容器中去。

public class TestFactoryBean implements FactoryBean {

    @Override
    public IHello getObject() {

        return (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName());
                return "hello FactoryBean";
            }
        });
    }

    @Override
    public Class getObjectType() {

        return IHello.class;
    }

}

interface IHello {

    String sayS();

    Integer sayI();
}
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        TestFactoryBean factoryBean = new TestFactoryBean();

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(IHello.class, () -> factoryBean.getObject()).getBeanDefinition();

        registry.registerBeanDefinition("hello", beanDefinition);
    }

}
@Import(TestImportBeanDefinitionRegistrar.class)
public class Bootstrap {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Bootstrap.class);

        IHello bean = context.getBean(IHello.class);
        System.out.println(bean);
        System.out.println(bean.sayS());
        // 这里会报错 因为方法的返回值是int 但是Proxy里面粗暴返回的String
        System.out.println(bean.sayI());
    }
}

你可能感兴趣的:(OpenFeign源码)