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的实现

  • 相关阅读:
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1553 数字反转(升级版)
    8.4 确定两个日期之间的月份数或年数
    (Java实现) 洛谷 P1553 数字反转(升级版)
  • 原文地址:https://www.cnblogs.com/ITPower/p/13437462.html
Copyright © 2011-2022 走看看