[Source learn together - two micro-services] Feign Source: Feign dynamic proxy configuration process

Foreword

Antecedent Review

Read on a main speaking registerBeanDefinitions () method of @EnableFeignClients, there is mainly
the corresponding configuration attribute annotations EnableFeignClients injection, the injection FeignClient corresponding attribute annotations.

Finally FeignClient generated corresponding bean, injected into Spring IOC container.

This lecture directory

Contents are as follows:

  1. registerFeignClient () Review
  2. FeignClientFactoryBean.getObject()解析
  3. Feign.builder () and Client () Logical Construction
  4. Feign create a dynamic proxy implementation details

Explanation

The original is not easy, should the reprint please indicate the source!

Blog address: flower count romantic
micro-channel public number: One branch count romantic flowers

Source code analysis

registerFeignClient () Review

Before the next review of the code:

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);
    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 = name + "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);
}

In the registerFeignClient()constructor of a BeanDefinitionBuilder object BeanDefinitionBuilder main function is to build a AbstractBeanDefinition, AbstractBeanDefinition is constructed as a final class is then registered with Spring BeanDefinitionHolder.

beanDefinition class FeignClientFactoryBean, acquired in Spring when the class is actually returned FeignClientFactoryBean class.

FeignClientFactoryBeanAs a realization of FactoryBean factory class, then every time you create entity classes in Spring Context of when it will call its getObject()methods.

FeignClientFactoryBean.getObject()解析

Here direct analysis FeignClientFactoryBean.getObject()method, this contains the principle Feign dynamic proxies.

Look at the code:

@Override
public Object getObject() throws Exception {
    // 可以类比于ribbon中的SpringClientFactory,每个服务都对应一个独立的spring容器
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // builder中包含contract、logLevel、encoder、decoder、options等信息
    Feign.Builder builder = feign(context);

    // 如果@FeignClient注解上没有指定url,说明是要用ribbon的负载均衡
    if (!StringUtils.hasText(this.url)) {
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        // 这里构建的url类似于:http://serviceA
        url += cleanPath();
        return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                this.name, 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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(
            this.type, this.name, url));
}

public <T> T getInstance(String name, Class<T> type) {
    // getContext是从SpringClientContext中获取,之前讲ribbon源码时讲过
    // 一个serviceName都会有自己的一个SpringClientContext上下文信息
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
            type).length > 0) {
        // 这里是获取到LoadBalancerFeignClient
        return context.getBean(type);
    }
    return null;
}

First FeignContext, we can be compared under the ribbon SpringClientFactory, each service call, there is a separate ILoadBalancer, IRule, IPing and so on, each service corresponds to a separate spring container, separate from the container, can be removed the associated service of their own stuff LoadBalancer like.

If we call a service, then such ServiceA, then the service will be associated with a spring container, FeignContext represents a separate container, with its own independent association of some components, such as Logger component, Decoder component, Encoder assembly and so on.

We can look FeignAutoConfigurationin:

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        // configurations是一个Map结构
        context.setConfigurations(this.configurations);
        return context;
    }
}

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        // FeignClientsConfiguration中会加载Encoder、Decoder、Logger等组件
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }
}

Here FeignContext can know the structure of the package which is actually a service instance (ServiceA) corresponding to the various components, which FeignClientsConfigurationis the default component configuration information loaded classes.

Next comes back FeignClientFactoryBean.getObject()in, then look at feign()methods:

protected Feign.Builder feign(FeignContext context) {
    // 从context中获取到默认Logger组件:Slf4jLogger
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);

    // 从context中找type:Feign.Builder.class 对应的组件信息
    // 然后往builder中放入各种组件信息
    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

    configureFeign(context, builder);

    return builder;
}

protected <T> T get(FeignContext context, Class<T> type) {
    // context中转载的有Logger组件信息,这里默认的是Slf4jLogger
    T instance = context.getInstance(this.name, type);
    if (instance == null) {
        throw new IllegalStateException("No bean found of type " + type + " for "
                + this.name);
    }
    return instance;
}

Here is a configuration Feign.builder () object which encapsulates various components or information. Wherein the Feign.builder FeignClientsConfigurationis initialized, generally employed isHystrixFeign.builder()

@Configuration
public class FeignClientsConfiguration {
    // 一般环境都会配置feign.hystrix.enabled = true,这里直接看HystrixFeign.builder();
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }
}

Next look at configureFeign()the method, the configuration information is read in application.properties. Here is a very interesting configuration:

configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);

If we configure feign, first specify a global profile, specified in the configuration for a service, then a service has a priority override the global configuration.

Photo summarize Feign.builder () build process:

02_Feign dynamic build process _1_-Feign.builder__ agent construct .jpg

Feign.builder () and Client () Logical Construction

Following the above, or getObject()the method to analyze, over the above analysis Feign.builder()constructed, and then look at the rest of the code below.

loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));

这里形式构造了一个HardCodeTarget对象,这个对象包含了接口类型(com.barrywang.service.feign.ServiceAFeignClient)、服务名称(ServiceA)、url地址(http://ServiceA),跟Feign.Builder、FeignContext,一起,传入了loadBalance()方法里去。

接着查看loadBalance() 方法:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
    // 这里还是从context中获取feignClient数据
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        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?");
}

protected <T> T getOptional(FeignContext context, Class<T> type) {
    return context.getInstance(this.name, type);
}

这里还是从context中获取Client.class对应的数据,我们继续查看FeignAutoConfiguration 类,但是并没有发现Feign.client相关的数据,查看FeignAutoConfiguration的依赖,可以找到FeignRibbonClientAutoConfiguration ,代码如下:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// 这里会import三个FeignLoadBalance配置
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

    @Bean
    @Primary
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory cachingLBClientFactory(
            SpringClientFactory factory) {
        return new CachingSpringLoadBalancerFactory(factory);
    }

    @Bean
    @Primary
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
        SpringClientFactory factory,
        LoadBalancedRetryPolicyFactory retryPolicyFactory,
        LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
        LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
        return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
    }

    // Options是超时相关的配置
    @Bean
    @ConditionalOnMissingBean
    public Request.Options feignRequestOptions() {
        return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    }
}

@Configuration
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null),
                cachingFactory, clientFactory);
    }
}

到了这里就知道了,这里Feign.client默认应该就是LoadBalancerFeignClient了。

到这继续用一张图总结下:

03_Feign dynamic build process _2_-Feign.client__ agent construct .jpg

创建Feign动态代理实现细节

接着上面代码,默认Feign.client()为LoadBalancerFeignClient, 然后将client加入到builder中。接着继续跟进targer相关:

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
        HardCodedTarget<T> target) {
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        // 这里又是通过Targer然后再context中获取默认配置
        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?");
}

protected <T> T get(FeignContext context, Class<T> type) {
    T instance = context.getInstance(this.name, type);
    if (instance == null) {
        throw new IllegalStateException("No bean found of type " + type + " for "
                + this.name);
    }
    return instance;
}

可以看到,这里又是通过Targeter.class从context中获取对应默认Targter。我们继续通过FeignAutoConfiguration中进行查找:

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {

    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();

    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }

    // 如果配置了feign.hystrix.HystrixFeign 则创建HystrixTargeter
    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }

    // 如果没有配置feign.hystrix.HystrixFeign 则创建DefaultTargeter
    @Configuration
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }
    }
}

在默认情况下,feign是和hystrix整合的,feign.hystrix.HystrixFeign会有配置,所以这里默认Targeter使用的是HystrixTargeter, 在loadBalance()方法中执行的targeter.target()方法就是执行HystrixTargeter.target()方法:

class HystrixTargeter implements Targeter {
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                        Target.HardCodedTarget<T> target) {
    // 判断Feign.builder()类型
    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);
    }

    // 最终都会执行feign.target()方法
    return feign.target(target);
}


public abstract class Feign {

  public static Builder builder() {
    return new Builder();
  }

  /**
   * Returns a new instance of an HTTP API, defined by annotations in the {@link Feign Contract},
   * for the specified {@code target}. You should cache this result.
   */
  public abstract <T> T newInstance(Target<T> target);

  public static class Builder {

    // 省略部分代码


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

    public Feign build() {
      // 构建一个SynchronousMethodHandler工厂
      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);
    }
  }
}

这里主要是build方法,构造了一个ReflectieFein对象,接着看它里面的newInstance()方法:

@Override
public <T> T newInstance(Target<T> target) {
    // nameToHandler是@FeignClient中的方法名对应的MethodHandler对象
    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 {
            // 将具体的method作为map的key值
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }

    // JDK动态代理 返回类似于:ReflectiveFeign$FeignInvocationHandler@7642
    // methodToHandler中包含Feign.builder()、Feign.client()等信息
    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;
}

这里就是使用了JDK动态代理,实际上返回的Feign动态代理的对象类似于:ReflectiveFeign$FeignInvocationHandler@7642

This is also the first to speak of us debug screenshots consistent, and to the principle here feign generate dynamic proxy objects have been very clear.

Finally, debug it, look at the dynamic proxy object finally produced:
image.png

to sum up

Finally, a summary chart rules Feign dynamic proxy generation:

  1. Generating Feign.builder (), which contains the Encoder, Decoder, Logger and other components, as well as related feign client application.properties configuration information
  2. Generation Feign.client (), the default is LoadBalancerFeignClient
  3. Generate Default Targter objects: HystrixTargter
  4. builder, client, targter JDK dynamic proxy generation by dynamic proxy objects feign

Photo Summary:

04_Feign dynamic proxy building process _3_- JDK dynamic proxy generation based on the principle .jpg

Declare

This article starting from my blog: https://www.cnblogs.com/wang-meng and public numbers: One ramiflorous be considered romantic , should reprint please indicate the source!

Interested partner may be concerned about the small number of individual public: One branch count romantic flowers

22.jpg

Guess you like

Origin www.cnblogs.com/wang-meng/p/12179844.html