zoukankan      html  css  js  c++  java
  • feign源码

    一. feign做了哪些事?

     上面是一段feign的代码, 系统是如何通过feign, 将reduceStock方法转换成stock服务的接口调用的呢?

    他做了两件事

    1. 讲reduceStock方法中的入参拼接到请求地址

    2. 讲请求的域名解析对应到指定的服务ip+端口号port----这一步使用到了ribbon进行服务器的选择

    3. 然后调用http请求, 发送请求到stock服务----通过ribbon封装的restTemplate, 发送请求

     二. feign的入口

    通常我们使用feign会怎么使用呢?

    第一步: 在启动类加上@EnableFeignClients注解

    package com.lxl.order;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @EnableDiscoveryClient
    @EnableFeignClients
    @SpringBootApplication
    public class OrderApplication {
    
        @LoadBalanced
        @Bean
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    
    }

    第二步: 在对应的client类上加上@FeignClient注解

    @FeignClient(name = "stock")
    public interface StockClient {
    
        @PostMapping("stock/reduce")
        String reduceStock();
    
        /**
         * http://stock-service/stock/deduct/{productId}/{stockCount}
         * @param productId
         * @param stockCount
         * @return
         */
        @PostMapping("reduce/count/{productId}/{stockCount}")
        String reduceStock(@PathVariable String productId, @PathVariable Integer stockCount);
    }

    那么看源码, 我们就从这两个注解入手. 

    首先看第一个注解@EnableFeignClients

    三. EnableFeignClients

    通过@EnableFeignClients可以直接定位到feign的源码位置. 

    首先, 还是第一步: 看spring.factories

    我们看到有一个FeignAutoConfiguration, 那么很有可能值就是feign的最开始的配置文件了. 我们来看看这个配置文件

    3.1 FeignAutoConfiguration

    这是一个配置类, 通常spring都是会通过一个AutoConfiguration来自动引入一些配置, 但是在feign的AutoConfiguration中没有引入太多的内容

    /*
     * Copyright 2013-2020 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      https://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.springframework.cloud.openfeign;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    
    import javax.annotation.PreDestroy;
    
    import feign.Client;
    import feign.Feign;
    import feign.httpclient.ApacheHttpClient;
    import feign.okhttp.OkHttpClient;
    import okhttp3.ConnectionPool;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.config.RegistryBuilder;
    import org.apache.http.conn.HttpClientConnectionManager;
    import org.apache.http.impl.client.CloseableHttpClient;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.client.actuator.HasFeatures;
    import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
    import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
    import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
    import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
    import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration;
    import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    /**
     * @author Spencer Gibb
     * @author Julien Roy
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Feign.class)
    @EnableConfigurationProperties({ FeignClientProperties.class,
            FeignHttpClientProperties.class })
    @Import(DefaultGzipDecoderConfiguration.class)
    public class FeignAutoConfiguration {
    
        @Autowired(required = false)
        private List<FeignClientSpecification> configurations = new ArrayList<>();
    
        @Bean
        public HasFeatures feignFeature() {
            return HasFeatures.namedFeature("Feign", Feign.class);
        }
    
        @Bean
        public FeignContext feignContext() {
            FeignContext context = new FeignContext();
            context.setConfigurations(this.configurations);
            return context;
        }
    
      // 这个类定义的是和Hystrix有关的内容 @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(); } } // the following configuration is for alternate feign clients if // ribbon is not on the class path. // see corresponding configurations in FeignRibbonClientAutoConfiguration // for load balanced ribbon clients.
       // 这个也是有引入条件的, 如果使用了ApacheHttpClient, 那么使用这个配置内容
    @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(); } } }
    // 这个类的引入条件是在OkHttpClient上. 也就是如果你使用了OkHttpClient类,那么会执行这段内容 @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); } } }

    如上注解, 我们看到在这个FeignAutoConfiguration中没有引入太多的东西. 很多内容都是有条件使用的. 

    在feign中, 有一个最重要的注解, 就是下面这个注解

     这是Spring 的注解了, 我们知道引入配置文件的方式有很多. 其中一个就是使用Import, 下面来具体看看FeignClientsRegistrar都做了那些事情

    3.2 FeignClientsRegistrar

    class FeignClientsRegistrar
            implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    这个类实现了ImportBeanDefinitionRegistrar, 那么就要重写他的一个方法registerBeanDefinitions

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

    1. registerDefaultConfiguration 看名字应该就能看出来, 这是一个引入默认的注册配置, 比如:我自定义在application中的配置, 在这时候读去出来, 进行加载

    2. registerFeignClients: 这是一个主要的方法, 看名字就能猜出来, 这是注册feignClients, 我们在客户端自定义了很多带有@FeignClient的类, 就是扫描这些类.

    public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
         // 这里得到了一个扫描器 ClassPathScanningCandidateComponentProvider scanner
    = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages;      // 扫描@EnableFeignClients注解,及其下面的属性,包和子包 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName());
         // 过滤带有@FeignClient注解的包 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); } } } }

    1. 扫描器scanner扫描带有@EnableFiegnClients注解的包及其子包, 扫描带有@FeignClient注解的类, 使用过滤器扫描获得.

    2. 把扫描的类放到spring容器里面

    1. 使用spring的动态代理获取带有@FeignClient注解的类,然后解析方法,将参数和路径进行拼接获得完整的url.

    2. 通过LoadBalancerFeignClient的execute方法解析上一步获得的url,将域名进行负载均衡后找到对应的ip:port. 然后进行http服务请求,后面就是ribbon的逻辑了,可以参考ribbon的实现

  • 相关阅读:
    Runloop运行循环的理解
    GCD dispatch_apply基本使用
    GCD信号量semaphore控制线程并发数
    多线程GCD dispatch_once_t/dispatch_barrier_<a>sync/dispatch_group_t
    iOS开发常用宏定义
    OC方法可变参数
    GCD的基本使用
    iOS实用小工具
    项目中实用第三方框架
    NSTimer内存泄漏问题
  • 原文地址:https://www.cnblogs.com/ITPower/p/13437462.html
Copyright © 2011-2022 走看看