美文网首页开源框架-SpringCloud系列微服务
SpringCloud解析七:Feign解析下

SpringCloud解析七:Feign解析下

作者: 一根线条 | 来源:发表于2020-01-13 17:28 被阅读0次

    前面章节已经介绍了Feign的基本原理,接下来继续解析其与Ribbon和Hystrix的集成逻辑,首先从自动配置类入手。

    自动配置类

    spring-cloud-openfeign-core-2.2.0.RELEASE.jar/META-INF/spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
    org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
    org.springframework.cloud.openfeign.FeignAutoConfiguration,\
    org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
    org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
    org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
    

    Feign中主要注解和配置类的执行顺序如下所示:

    执行顺序

    1,FeignRibbonClientAutoConfiguration

    @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
            matchIfMissing = true)
    @Configuration(proxyBeanMethods = false)
    //在FeignAutoConfiguration执行前运行被配置
    @AutoConfigureBefore(FeignAutoConfiguration.class)
    //读取配置信息
    @EnableConfigurationProperties({ FeignHttpClientProperties.class })
    // Order is important here, last should be the default, first should be optional
    // see
    // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
    @Import({ HttpClientFeignLoadBalancedConfiguration.class,
            OkHttpFeignLoadBalancedConfiguration.class,
            DefaultFeignLoadBalancedConfiguration.class })
    public class FeignRibbonClientAutoConfiguration {
    
        //此处的SpringClientFactory就是Ribbon的核心对象
        @Bean
        @Primary
        @ConditionalOnMissingBean
        @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
        public CachingSpringLoadBalancerFactory cachingLBClientFactory(
                SpringClientFactory factory) {
            return new CachingSpringLoadBalancerFactory(factory);
        }
    
        //此处的SpringClientFactory就是Ribbon的核心对象
        @Bean
        @Primary
        @ConditionalOnMissingBean
        @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
        public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
                SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
            return new CachingSpringLoadBalancerFactory(factory, retryFactory);
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Request.Options feignRequestOptions() {
            return LoadBalancerFeignClient.DEFAULT_OPTIONS;
        }
    }
    

    在这里创建了CachingSpringLoadBalancerFactory实例对象,该对象将被导入的HttpClientFeignLoadBalancedConfiguration或OkHttpFeignLoadBalancedConfiguration或DefaultFeignLoadBalancedConfiguration使用,如HttpClientFeignLoadBalancedConfiguration:

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    @Import(HttpClientFeignConfiguration.class)
    class HttpClientFeignLoadBalancedConfiguration {
    
        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory, HttpClient httpClient) {
            ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }
    

    使用CachingSpringLoadBalancerFactory 、SpringClientFactory 来创建LoadBalancerFeignClient实例对象。

    CachingSpringLoadBalancerFactory源码如下

    public class CachingSpringLoadBalancerFactory {
    
        protected final SpringClientFactory factory;
    
        protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;
    
        private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
    
        public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
            this.factory = factory;
        }
    
        public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
                LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
            this.factory = factory;
            this.loadBalancedRetryFactory = loadBalancedRetryPolicyFactory;
        }
    
        public FeignLoadBalancer create(String clientName) {
            FeignLoadBalancer client = this.cache.get(clientName);
            if (client != null) {
                return client;
            }
            IClientConfig config = this.factory.getClientConfig(clientName);
            ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
            ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
                    ServerIntrospector.class);
            client = this.loadBalancedRetryFactory != null
                    ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                            this.loadBalancedRetryFactory)
                    : new FeignLoadBalancer(lb, config, serverIntrospector);
            this.cache.put(clientName, client);
            return client;
        }
    }
    

    在此处得到Ribbon的负载均衡器,然后创建具有重试能力的RetryableFeignLoadBalancer或不具有重试能力的FeignLoadBalancer对象并返回。

    LoadBalancerFeignClient源码如下

    //feign.Client类型
    public class LoadBalancerFeignClient implements Client {
    
        static final Request.Options DEFAULT_OPTIONS = new Request.Options();
    
        private final Client delegate;
    
        private CachingSpringLoadBalancerFactory lbClientFactory;
    
        private SpringClientFactory clientFactory;
    
        public LoadBalancerFeignClient(Client delegate,
                CachingSpringLoadBalancerFactory lbClientFactory,
                SpringClientFactory clientFactory) {
            this.delegate = delegate;
            this.lbClientFactory = lbClientFactory;
            this.clientFactory = clientFactory;
        }
    
        static URI cleanUrl(String originalUrl, String host) {
            String newUrl = originalUrl;
            if (originalUrl.startsWith("https://")) {
                newUrl = originalUrl.substring(0, 8)
                        + originalUrl.substring(8 + host.length());
            }
            else if (originalUrl.startsWith("http")) {
                newUrl = originalUrl.substring(0, 7)
                        + originalUrl.substring(7 + host.length());
            }
            StringBuffer buffer = new StringBuffer(newUrl);
            if ((newUrl.startsWith("https://") && newUrl.length() == 8)
                    || (newUrl.startsWith("http://") && newUrl.length() == 7)) {
                buffer.append("/");
            }
            return URI.create(buffer.toString());
        }
    
        //执行请求
        @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);
                return lbClient(clientName)
                        .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
            }
            catch (ClientException e) {
                IOException io = findIOException(e);
                if (io != null) {
                    throw io;
                }
                throw new RuntimeException(e);
            }
        }
    
        IClientConfig getClientConfig(Request.Options options, String clientName) {
            IClientConfig requestConfig;
            if (options == DEFAULT_OPTIONS) {
                requestConfig = this.clientFactory.getClientConfig(clientName);
            }
            else {
                requestConfig = new FeignOptionsClientConfig(options);
            }
            return requestConfig;
        }
    
        protected IOException findIOException(Throwable t) {
            if (t == null) {
                return null;
            }
            if (t instanceof IOException) {
                return (IOException) t;
            }
            return findIOException(t.getCause());
        }
    
        public Client getDelegate() {
            return this.delegate;
        }
    
        private FeignLoadBalancer lbClient(String clientName) {
            return this.lbClientFactory.create(clientName);
        }
    
        static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
    
            FeignOptionsClientConfig(Request.Options options) {
                setProperty(CommonClientConfigKey.ConnectTimeout,
                        options.connectTimeoutMillis());
                setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
            }
    
            @Override
            public void loadProperties(String clientName) {
    
            }
    
            @Override
            public void loadDefaultValues() {
    
            }
        }
    }
    

    通过以上步骤以及完成了Client的创建,并且与Ribbon紧密的结合在一起。


    2,FeignLoadBalancerAutoConfiguration

    @ConditionalOnClass(Feign.class)
    @ConditionalOnBean(BlockingLoadBalancerClient.class)
    //在FeignAutoConfiguration执行前运行本配置
    @AutoConfigureBefore(FeignAutoConfiguration.class)
    //在FeignRibbonClientAutoConfiguration执行后运行本配置
    @AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
    //读取配置数据
    @EnableConfigurationProperties(FeignHttpClientProperties.class)
    @Configuration(proxyBeanMethods = false)
    // Order is important here, last should be the default, first should be optional
    // see
    // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
    @Import({ HttpClientFeignLoadBalancerConfiguration.class,
            OkHttpFeignLoadBalancerConfiguration.class,
            DefaultFeignLoadBalancerConfiguration.class })
    class FeignLoadBalancerAutoConfiguration {
    
    }
    

    可见,其导入了三个配置类,例如HttpClientFeignLoadBalancerConfiguration

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnBean(BlockingLoadBalancerClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    @Import(HttpClientFeignConfiguration.class)
    class HttpClientFeignLoadBalancerConfiguration {
    
        /**
        * 其中的BlockingLoadBalancerClient.class在
        * spring-cloud-loadbalancer-2.2.0.RELEASE.jar包中
        * 注意:由于前面的阶段已经创建了Client类型实例【LoadBalancerFeignClient】,所以这里并不会生效
        */
        @Bean
        @ConditionalOnMissingBean
        public Client feignClient(BlockingLoadBalancerClient loadBalancerClient,
                HttpClient httpClient) {
            ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
            return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
        }
    }
    

    3,FeignAutoConfiguration

    • FeignAcceptGzipEncodingAutoConfiguration
    • FeignContentGzipEncodingAutoConfiguration
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Feign.class)
    @EnableConfigurationProperties({ FeignClientProperties.class,
            FeignHttpClientProperties.class })
    @Import(DefaultGzipDecoderConfiguration.class)
    public class FeignAutoConfiguration {
        //收集前面创建的Feign客户端的配置
        @Autowired(required = false)
        private List<FeignClientSpecification> configurations = new ArrayList<>();
    
        @Bean
        public HasFeatures feignFeature() {
            return HasFeatures.namedFeature("Feign", Feign.class);
        }
            
        /**
        * 创建上下文。 
        * 同Ribbon创建SpringFactory一样,FeignContext会为每个Feign客户端创建一个ApplicationContext。
        * 使用FeignClientsConfiguration作为默认的配置
        */
        @Bean
        public FeignContext feignContext() {
            FeignContext context = new FeignContext();
            context.setConfigurations(this.configurations);
            return context;
        }
    
        ////////如果当前环境中具有feign.hystrix.HystrixFeign类型则创建HystrixTargeter,否则创建DefaultTargeter/////////
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
        protected static class HystrixFeignTargeterConfiguration {
            @Bean
            @ConditionalOnMissingBean
            public Targeter feignTargeter() {
                return new HystrixTargeter();
            }
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
        protected static class DefaultFeignTargeterConfiguration {
            @Bean
            @ConditionalOnMissingBean
            public Targeter feignTargeter() {
                return new DefaultTargeter();
            }
        }
    
        ///////////以下是用于创建Fegin使用的客户端////////////
        /**
        *  如果ribbon不在类路径上,则以下配置适用于备用外部客户端。
        *  请参阅FeignRibbonClientAutoConfiguration中有关负载平衡功能区客户端的相应配置。
        */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(ApacheHttpClient.class)
        @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
        @ConditionalOnMissingBean(CloseableHttpClient.class)
        @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
        protected static class HttpClientFeignConfiguration {
    
            private final Timer connectionManagerTimer = new Timer(
                    "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
    
            @Autowired(required = false)
            private RegistryBuilder registryBuilder;
    
            private CloseableHttpClient httpClient;
    
            @Bean
            @ConditionalOnMissingBean(HttpClientConnectionManager.class)
            public HttpClientConnectionManager connectionManager(
                    ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                    FeignHttpClientProperties httpClientProperties) {
                final HttpClientConnectionManager connectionManager = connectionManagerFactory
                        .newConnectionManager(httpClientProperties.isDisableSslValidation(),
                                httpClientProperties.getMaxConnections(),
                                httpClientProperties.getMaxConnectionsPerRoute(),
                                httpClientProperties.getTimeToLive(),
                                httpClientProperties.getTimeToLiveUnit(),
                                this.registryBuilder);
                this.connectionManagerTimer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        connectionManager.closeExpiredConnections();
                    }
                }, 30000, httpClientProperties.getConnectionTimerRepeat());
                return connectionManager;
            }
    
            @Bean
            public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
                    HttpClientConnectionManager httpClientConnectionManager,
                    FeignHttpClientProperties httpClientProperties) {
                RequestConfig defaultRequestConfig = RequestConfig.custom()
                        .setConnectTimeout(httpClientProperties.getConnectionTimeout())
                        .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
                        .build();
                this.httpClient = httpClientFactory.createBuilder()
                        .setConnectionManager(httpClientConnectionManager)
                        .setDefaultRequestConfig(defaultRequestConfig).build();
                return this.httpClient;
            }
    
            @Bean
            @ConditionalOnMissingBean(Client.class)
            public Client feignClient(HttpClient httpClient) {
                return new ApacheHttpClient(httpClient);
            }
    
            @PreDestroy
            public void destroy() throws Exception {
                this.connectionManagerTimer.cancel();
                if (this.httpClient != null) {
                    this.httpClient.close();
                }
            }
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(OkHttpClient.class)
        @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
        @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
        @ConditionalOnProperty("feign.okhttp.enabled")
        protected static class OkHttpFeignConfiguration {
    
            private okhttp3.OkHttpClient okHttpClient;
    
            @Bean
            @ConditionalOnMissingBean(ConnectionPool.class)
            public ConnectionPool httpClientConnectionPool(
                    FeignHttpClientProperties httpClientProperties,
                    OkHttpClientConnectionPoolFactory connectionPoolFactory) {
                Integer maxTotalConnections = httpClientProperties.getMaxConnections();
                Long timeToLive = httpClientProperties.getTimeToLive();
                TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
                return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
            }
    
            @Bean
            public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
                    ConnectionPool connectionPool,
                    FeignHttpClientProperties httpClientProperties) {
                Boolean followRedirects = httpClientProperties.isFollowRedirects();
                Integer connectTimeout = httpClientProperties.getConnectionTimeout();
                Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
                this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
                        .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                        .followRedirects(followRedirects).connectionPool(connectionPool)
                        .build();
                return this.okHttpClient;
            }
    
            @PreDestroy
            public void destroy() {
                if (this.okHttpClient != null) {
                    this.okHttpClient.dispatcher().executorService().shutdown();
                    this.okHttpClient.connectionPool().evictAll();
                }
            }
    
            @Bean
            @ConditionalOnMissingBean(Client.class)
            public Client feignClient(okhttp3.OkHttpClient client) {
                return new OkHttpClient(client);
            }
        }
    }
    

    默认配置类FeignClientsConfiguration源码

    @Configuration(proxyBeanMethods = false)
    public class FeignClientsConfiguration {
    
        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        @Autowired(required = false)
        private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
    
        @Autowired(required = false)
        private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
    
        @Autowired(required = false)
        private Logger logger;
    
        @Autowired(required = false)
        private SpringDataWebProperties springDataWebProperties;
    
        @Bean
        @ConditionalOnMissingBean
        public Decoder feignDecoder() {
            return new OptionalDecoder(
                    new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
        }
    
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
        public Encoder feignEncoder() {
            return new SpringEncoder(this.messageConverters);
        }
    
        @Bean
        @ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
        @ConditionalOnMissingBean
        public Encoder feignEncoderPageable() {
            PageableSpringEncoder encoder = new PageableSpringEncoder(
                    new SpringEncoder(this.messageConverters));
            if (springDataWebProperties != null) {
                encoder.setPageParameter(
                        springDataWebProperties.getPageable().getPageParameter());
                encoder.setSizeParameter(
                        springDataWebProperties.getPageable().getSizeParameter());
                encoder.setSortParameter(
                        springDataWebProperties.getSort().getSortParameter());
            }
            return encoder;
        }
    
        //接口以及方法元数据解析器
        @Bean
        @ConditionalOnMissingBean
        public Contract feignContract(ConversionService feignConversionService) {
            return new SpringMvcContract(this.parameterProcessors, feignConversionService);
        }
    
        @Bean
        public FormattingConversionService feignConversionService() {
            FormattingConversionService conversionService = new DefaultFormattingConversionService();
            for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
                feignFormatterRegistrar.registerFormatters(conversionService);
            }
            return conversionService;
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Retryer feignRetryer() {
            return Retryer.NEVER_RETRY;
        }
    
        /**
        * Feign.Builder: 设置发送http请求的相关参数,
        * 比如http客户端,重试策略,编解码,超时时间等等
        */
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        public Feign.Builder feignBuilder(Retryer retryer) {
            return Feign.builder().retryer(retryer);
        }
    
        @Bean
        @ConditionalOnMissingBean(FeignLoggerFactory.class)
        public FeignLoggerFactory feignLoggerFactory() {
            return new DefaultFeignLoggerFactory(this.logger);
        }
    
        @Bean
        @ConditionalOnClass(name = "org.springframework.data.domain.Page")
        public Module pageJacksonModule() {
            return new PageJacksonModule();
        }
    
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
        protected static class HystrixFeignConfiguration {
    
            @Bean
            @Scope("prototype")
            @ConditionalOnMissingBean
            @ConditionalOnProperty(name = "feign.hystrix.enabled")
            public Feign.Builder feignHystrixBuilder() {
                return HystrixFeign.builder();
            }
        }
    }
    

    在上面的几个步骤中创建了feign.Client类型的实例对象(如:LoadBalancerFeignClient),也创建了org.springframework.cloud.openfeign.Targeter类型的实例对象(如:HystrixTargeter),从而与前面章节结束的内容紧密的联系在了一起,从而实现了Feign与Ribbon的集成。


    总结:

    一:使用SpringCloud Feign的请求调用过程

    1,添加@EnableFeignClients启动Feign(可指定扫描@FeignClient注解的包路径)
    2,在某个接口上添加@FeignClient(name="hostNmae" )注解
    3,在使用的地方注入该接口即可使用

    二:SpringCloud Feign实现过程

    SpringCloud Feign根本思想是通过Java的动态代理给接口添加实现类,主要经过以下几个步骤

    1,执行@EnableFeignClients

    1)设置全局配置来覆盖默认的(如果配置)
    2)根据@EnableFeignClients设置的value、basePackages、basePackageClasses作为扫描@FeignClient的根路径;如果都没设置,则使用@EnableFeignClients所在的包路径作为扫描的根路径
    3)读取@FeignClient注解上的contextId或value或name或serviceId作为Feign客户端名称
    4)读取@FeignClient注解上的configuration属性作为该客户端的配置,将与全局默认配置相同的配置覆盖(可能没设置)
    5)为@FeignClient客户端(该接口)创建FeignClientFactoryBean工厂Bean,同时会将接口的全类名(type)、注解上配置的客户端名称(name)、contextId、path、url、fallback、fallbackFactory等值复制给FeignClientFactoryBean


    当在某个地方调用接口的时候,主要经过了以下几个步骤【通过Java动态代理创建接口的实例对象】
    6)根据FeignClientFactoryBean中设置的contextId,从FeignContext中得到Feign.Builder对象、feign.Client类型实例(与Ribbon集成时为LoadBalancerFeignClient)、org.springframework.cloud.openfeign.Targeter类型实例
    7)接着调用Targeter类型实例(主要为HystrixTargeter与DefaultTargeter)的target(Feign.Builder,FeignContext ,HardCodedTarget<T>)方法并将结果返回【此结果就是生成的接口的代理对象】

    对于Feign与Hystrix的集成都在HystrixTargeter中,其内部最终会在HystrixInvocationHandler中创建一个HystrixCommand并执行。


    以下两个依赖包需要重点关注

    1. spring-cloud-netflix-ribbon-2.2.0.RELEASE.jar
    2. feign-hystrix-10.4.0.jar

    相关文章

      网友评论

        本文标题:SpringCloud解析七:Feign解析下

        本文链接:https://www.haomeiwen.com/subject/twksactx.html