zoukankan      html  css  js  c++  java
  • 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程

    前言

    前情回顾

    上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是
    将EnableFeignClients注解对应的配置属性注入,将FeignClient注解对应的属性注入。

    最后是生成FeignClient对应的bean,注入到Spring 的IOC容器。

    本讲目录

    目录如下:

    1. registerFeignClient()回顾
    2. FeignClientFactoryBean.getObject()解析
    3. Feign.builder()及client()构建逻辑
    4. 创建Feign动态代理实现细节

    说明

    原创不易,如若转载 请标明来源!

    博客地址:一枝花算不算浪漫
    微信公众号:壹枝花算不算浪漫

    源码分析

    registerFeignClient()回顾

    回顾下之前的代码:

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

    registerFeignClient()方法中构造了一个BeanDefinitionBuilder对象,BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition,AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder 然后注册到Spring中。

    beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是FeignClientFactoryBean类。

    FeignClientFactoryBean作为一个实现了FactoryBean的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的getObject()方法。

    FeignClientFactoryBean.getObject()解析

    这里直接分析FeignClientFactoryBean.getObject()方法,这里包含着Feign动态代理的原理。

    先看下代码:

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

    首先是FeignContext ,我们可以类比下ribbon中的SpringClientFactory, 每个服务的调用,都有一个独立的ILoadBalancer、IRule、IPing等等,每个服务都对应一个独立的spring容器,从那个独立的容器中,可以取出这个服务关联的属于自己的LoadBalancer之类的东西。

    如果我们调用一个服务的话,比如ServiceA,那么这个服务就会关联一个spring容器,FeignContext就代表一个独立的容器,关联着自己独立的一些组件,例如Logger组件、Decoder组件、Encoder组件等等。

    我们可以看下FeignAutoConfiguration中:

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

    这里可以知道FeignContext的结构,里面其实就是封装了一个服务实例(ServiceA)对应的各种组件,其中FeignClientsConfiguration是加载默认的组件信息配置类。

    接下来还是回到FeignClientFactoryBean.getObject()中,接着看feign()方法:

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

    这里是构造一个Feign.builder()对象,里面还是封装了各种组件信息。其中Feign.builder在FeignClientsConfiguration被初始化,一般使用的是HystrixFeign.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();
    		}
    	}
    }
    

    接着看configureFeign() 方法,这个方法是读取application.properties中的配置信息。这里有个很有趣的配置:

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

    如果我们配置feign,先指定一个全局配置,在指定针对于某个服务的配置,那么某个服务配置的优先级会覆盖全局的配置。

    一张图总结下Feign.builder()构建的过程:

    02_Feign动态代理构建过程_1_-Feign.builder__构建.jpg

    Feign.builder()及client()构建逻辑

    还是接着上面getObject() 方法去分析,上面分析完了Feign.builder()的构建,下面接着看看剩下的代码。

    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动态代理构建过程_2_-Feign.client__构建.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

    这也和我们第一讲中的debug截图一致了,到了这里feign动态代理对象的生成原理都已经很清楚了。

    最后debug一下,看下最终生成的动态代理对象:
    image.png

    总结

    最后用一张图总结Feign动态代理生成的规则:

    1. 生成Feign.builder(),里面包含Encoder、Decoder、Logger等组件,还有application.properties中相关的feign client配置信息
    2. 生成Feign.client(),默认为LoadBalancerFeignClient
    3. 生成默认Targter对象:HystrixTargter
    4. builder、client、targter 通过JDK动态代理生成feign动态代理对象

    一张图总结:

    04_Feign动态代理构建过程_3_-基于JDK动态代理生成原理.jpg

    申明

    本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

    感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

    22.jpg

  • 相关阅读:
    AS2介绍
    .net 资源大收藏
    智能客户端(SmartClient)(转载)
    [WPF Bug清单]之(3)——暗中创建文件的打开文件对话框
    实例分析SharpDevelop代码完成功能
    [WPF Bug清单](序)与之(1)——可以多选的单选ListBox
    实例分析SharpDevelop代码完成功能(续)——添加对Boo语言的支持
    [WPF Bug清单]之(2)——RadioButton的IsChecked绑定失效
    让WPF窗体程序支持命令行方式运行的三种方式
    基于文法分析的表达式计算器的实现
  • 原文地址:https://www.cnblogs.com/wang-meng/p/12179844.html
Copyright © 2011-2022 走看看