Анализ точки и принципа расширения Spring Cloud Feign (исходный код)

Spring Cloud

задний план

Среди технических решений для удаленного вызова Spring Cloud Openfeign можно назвать простым в запуске и использовании. Как член семейства весенних облаков, он широко используется. Понимание Spring Framework, Spring Boot, Spring Cloud — это вопрос технического персонала. На пути к продвинутому это необходимо сделать.

Исходный код отладки, интуитивное преимущество состоит в том, чтобы изучить стандартный стиль кода, идеи кодирования, методы и навыки реализации, чтобы улучшить свои собственные способности.

награда

Эта статья посвящена разработкеfeignПоказан принцип реализации и простой пример его использования. Ведь эта статья позволяет получить не толькоfeignиспользования и, что более важно,feignПринцип проработки может извлекать использованные нами с вами очки знаний, обеспечивать некую основу и фрагменты для собственной реализации аннотации @EnableXXX, стремиться к прорывам и производить свои собственные вещи.

Цель этой статьи состоит в том, чтобы

1. Сделайте это понятнымfeignЗагрузка компонентов - это разбор, особенно объяснение основного исходного кода ключевых компонентов;

2. Когда приходят запросы пользователей,feignкак будетfeignМетод решаетhttp requestзапрос информации.

3. Кроме того, на этой основе ориентироваться на изложениеfeignКакую ценность приносит нам весь процесс: два аспекта: точки повторного использования и точки расширения

1. Точка повторного использования

1. 如何加载一个package下指定的class文件即生成BeanDefinition放入spring容器
2. Class<T>类生成T的instance
3. 如何获取spring中的BeanDefinition、Object

2. Точка расширения

1. 扩展RuquestInteceptor,实现Get方式接受body data
2. 自定义@EnableFeignClients.defaultConfiguration的值
3. 自定义@FeignClient.configuration的值
4. properties.yml和@Configuration声明的feign组件的优先级
5. 自定义@EnableXXX组件,解决其他的问题

20191109101703.png

Пример

Ниже приведен простейший пример использования имитации.分别定义一个启动类、controller、feign接口. Как следует, используйте это в качестве материалаfeign源码分析

@SpringBootApplication
@EnableFeignClients("com.skyler.manager")
@EnableDiscoveryClient
public class SkylerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkylerApplication.class, args);
    }
}

@RestController
@RequestMapping("/brand")
public class BrandController {
    @Autowired private BrandFeignClient brandFeignClient;

    @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResultVO create(@RequestBody BrandParam param) {
        return brandFeignClient.create(param);
    }
}

@FeignClient(value = "${provider.application:serverB}", contextId = "BrandFeignClient")
public interface BrandFeignClient {
    
    @PostMapping(value = "api/brand/create", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    ResultDTO create(@RequestBody BrandParam param);

    @GetMapping("api/brand/query")
    ResultDTO<List<BrandDto>> query(@RequestParam("id") Long id, @RequestParam("code") String code);
}

симулировать класс ключевого компонента

Список первымfeign关键место, у вас сложилось общее впечатление, надеюсь, вы сможете осознанно проанализировать исходный код重点照顾эти ключевые моменты.

ключевой класс эффект иллюстрировать
@EnableFeignClient Объявить идентификатор введенного компонента feign и нести ответственность за синтаксический анализ @FeignClient.
@FeignClient Объявите интерфейс удаленного вызова для бизнес-стороны, чтобы завершить удаленный вызов
FeignClientsRegistrar Загрузите все файлы .class @FeignClient, сгенерируйте beanDefinitioin и beanInstance в контейнер Spring.
FeignClientFactoryBean Завершите агрегацию компонентов, необходимых для имитации удаленных вызовов, а также создание прокси-класса @FeignClient.
FeignAutoConfiguration Подкласс AutoConfiguration, определяющий @Bean FeignContext, @Bean xxxHttpClient и т. д.
FeignClientsConfiguration Значение конфигурации по умолчанию «Конфигурация Feign», @EnableFeignClients.defaultConfiguration. Если значение не задано, по умолчанию используется FeignClientsConfiguration.

Принцип загрузки кода Feign

На следующем рисунке показан путь, по которому все проекты загрузки Spring загружаются при загрузке классов @Configuration (кроме FeignClientsRegistrar). См.

图一

.class компонентов, связанных с feign, преобразуется в BeanDefinition.

Этот процесс можно разделить на две части:FeignAutoConfiguration类和FeignClientsConfiguration类Ждать,@EnableFeignClients和@FeignClient.

Процесс загрузки первого такой же, как и логика загрузки всех компонентов Spring Boot, как показано на рисунке 1;feignсобственный способ загрузки. Разумеется, начало процесса загрузки у обоих одинаковое. то есть изspringApplication.run()прибытьConfigurationClassPostProcessor.processConfigBeanDefinitions()то же самое и всеspring bootЗапуск проекта загружает общую логику. Но отprocessConfigBeanDefinitions()Метод начинает выглядеть иначе.FeignAutoConfiguration类和FeignClientsConfiguration类Загрузка стартов внутри метода315行的parser.parse(candidates),а также@EnableFeignClients和@FeignClientЗагрузка начинается в327行的this.reader.loadBeanDefinitions(configClasses). На самом деле, с другой точки зрения, можно сказать, чтоFeignAutoConfiguration类和FeignClientsConfigurationЗагрузка класса является общей,@EnableFeignClients和@FeignClientГруз особенный и представляет собой ветку, вытянутую из определенной точки общего. Так что специальное делается на родовой основе. из двух разных точек в соответствующие конкретные.class-->BeanDefinitionпроцесс загрузки. С той же целью, только другим способом. Подробнее об исходном коде и принципе двух загрузок поговорим ниже.

""" Стоит отметить, что эти два метода загрузки являются точками расширения в нашем фактическом процессе разработки: кодовом процессе преобразования .class в beanDefiniton. переписать их, чтобы завершить нашу собственную фактическую логику

класс FeignAutoConfiguration и класс FeignClientsConfiguration и т. д.

Как упоминалось ранее, их загрузка начинается в методе ConfigurationClassPostProcessor.processConfigBeanDefinitions().315行: 的parser.parse(candidates). Начнем отсюда

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        // 检查beanDefinition的beanClass是否为标识了@Configuration的class,符合的放入集合
        if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    // 根据@Order给集合中的对象排序,也就是对象被加载的顺序
    configCandidates.sort((bd1, bd2) -> {
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    do {`
        // FeignAutoConfiguration类和FeignClientsConfiguration类等通用组件的加载开始处
        // 方法功能为以candidates为起点进行向下加载。一般为项目的启动类,如这里的SkylerApplication
        parser.parse(candidates);
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
        }
        // @EnableFeignClients和@FeignClient的加载处,当然通用的加载也会走这里
        // 方法功能为解析每个ConfigurationClass,看看有没有@Bean @Import @ImportResource @Scope注解,如果如解析他们形成BeanDefinition,放入beanFactory容器中
        this.reader.loadBeanDefinitions(configClasses);

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            for (String candidateName : newCandidateNames) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory)) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    while (!candidates.isEmpty());
}

Подробная загрузка начинается со строки parser.parse(candidates), подробности см. в todo. На этом загрузка и синтаксический анализ классов FeignAutoConfiguration и FeignClientsConfiguration завершены. Суть заключается в загрузке методов @Bean в классе (таких как feignEncoder(), feignDecoder(), feignBuilder() и т. д.) в BeanDefinition и помещении их в контейнер spring beanFactory для подготовки к преобразованию BeanDefinition в BeanInstance. Подробный процесс преобразования класса, указанного @Configuration, из BeanDefinition в BeanInstance см. в разделе Анализ класса todo@Configuration.

@EnableFeignClients и @FeignClient

Загрузка этой части осуществляется из метода processConfigBeanDefinitions().327行的this.reader.loadBeanDefinitions(configClasses)Начните и далее ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass() внутри этого метода. То есть начать обработку объекта ConfigurationClass. При обработке ConfigurationClass (SkylerApplication) будет запущено разрешение @EnableFeignClients. Принцип заключается в том, что @EnableFeignClients помечен в классе SkylerApplication, который представляет @SpringBootApplication (включая @Configuration), то есть класс SkylerApplication помечен аннотацией @Configuration, поэтому SkylerApplication будет проанализирован в объект ConfigurationClass. А @EnableFeignClients содержит аннотацию @Import, поэтому, когда ConfigurationClassPostProcessor анализирует объект ConfigurationClass(SkylerApplication), он загружается в класс FeignClientsRegistrar с аннотацией @Import, встроенной в @EnableFeignClients. Кроме того, FeignClientsRegistrar является подклассом ImportBeanDefinitionRegistrar, поэтому ConfigurationClassBeanDefinitionReader будет анализировать объект FeignClientsRegistrar при разборе типа ImportBeanDefinitionRegistrar, то есть начинается разбор и загрузка компонентов, связанных с Feign.

Поскольку FeignClientsRegistrar имеет тип ImportBeanDefinitionRegistrar, он перегружает метод registerBeanDefinitions() для реализации функции разбора @FeignClient, которая также является основной функцией класса FeignClientsRegistrar. Следующий код

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    // 解析@EnableFeignClients的defaultConfiguration属性,用于feign的全局设置       
    registerDefaultConfiguration(metadata, registry);
    // 解析@FeignClient注解
    registerFeignClients(metadata, registry);
}

Входные параметры метода: метаданные — это метааннотационная информация ConfigurationClass, то есть аннотационная информация класса SkylerApplication, реестр — ссылка на объект DefaultListableBeanFactory. BeanDefinition, сгенерированный синтаксическим анализом, помещается в контейнер spring beanFactory.

private void registerDefaultConfiguration(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    // 获取EnableFeignClients注解的属性及值        
    Map<String, Object> defaultAttrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
    // 获取的属性及name存入spring的BeanFactory容器内
    registerClientConfiguration(registry, name,
            defaultAttrs.get("defaultConfiguration"));
    }
}

Точка расширения в нашей фактической разработке здесь: когда @EnableFeignClients вводится, значение его атрибута defaultConfiguration может быть настроено, чтобы реализовать нашу собственную конфигурацию Feign. Например, переписывание шифрования и расшифровки информации об ответе на запрос Feign, откат, fallbackFactory и т. д.

Давайте сосредоточимся на логике метода registerFeignClients(): загрузите файл .class, помеченный @FeignClient, проанализируйте и получите квалифицированные классы, сгенерируйте BeanDefinition и поместите его в контейнер spring beanFactory. код показывает, как показано ниже

public void registerFeignClients(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    // 实例化对象
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
            FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
            : (Class<?>[]) attrs.get("clients");
    // 确定要搜索的package
    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)));
    }

    // 加载package将标注了@FeignClient的.class转化为BeanDefinition,放入spring beanFactory容器
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                 
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());

                // 获取@FeignClient的configuration属性值,生成BeanDefinition放入spring beanFactory容器
                // 还记得@EnableFeignClient.defaultConfiguration的属性值吗,对比@FeignClient.configuration,所以前者是所有@FeignClient使用,后者是单个@FeignClient使用,后者优先级高于前者
                registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
                // 将每个@FeignClient解析,生成BeanDefinition放入spring BeanFactory容器
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

Для поиска подходящих классов @FeignClient этот метод разделен на два этапа.

  1. Сначала определите каталог пакета для поиска
  2. Во-вторых, получите и проанализируйте классы @FeignClient из этих каталогов пакетов.

Метод разделен на три ситуации для определения каталога пакета: приоритет — получить значение атрибута из @EnableFeignClients.clients для определения каталога пакета, второй приоритет — получить значение атрибута из значения атрибута, basePackages, basePackageClasses из EnableFeignClients для определения каталога пакета; последний Уровень приоритета из пакета того класса, где находится @EnableFeignClient, тем самым определяя каталог пакета.

После определения каталога пакета начните загружать файл .class, отмеченный @FeignClient, в каталоге пакета. Класс ClassPathScanningCandidateComponentProvider используется для загрузки файла .class, переменная resourceLoader этого класса предоставляет classLoader для загрузки файла .class, в то же время переменная includeFilters указывает, какие классы должны быть преобразованы в BeanDefinitions. Наконец, поместите BeanDefinition в контейнер Spring BeanFactory. Чтобы максимально не мешать фиктивной части, загрузка .class и преобразование в BeanDefinition здесь описываться не будут.Для подробного кода см. метод ClassPathScanningCandidateComponentProvider.scanCandidateComponents().

Специальное примечание: Вот точка расширения в нашей фактической разработке: загрузите файлы .class, помеченные указанными аннотациями, в указанный каталог пакета, преобразуйте их в BeanDefinitiions и поместите их в контейнер spring beanFactory. детали следующим образом

第一步:
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
第二步:
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
第三步:
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); //registry为BeanDefinitionRegistry及子类或DefaultListableBeanFactory

После вышеуказанных трех шагов вы можете преобразовать файл .class в указанный пакет в BeanDefinition, а затем поместить его в контейнер BeanFactory Spring. Например: в реальной разработке вам нужно загрузить файл .class с аннотацией @LoginAccess в com.yourcompany.projectName, использовать приведенный выше код напрямую, и все будет в порядке с небольшой модификацией.

При преобразовании каждого @FeignClient для создания BeanDefinition в контейнер Spring BeanFactory обратите внимание на следующее: значение beanClass сгенерированного BeanDefinition имеет тип FeignClientFactoryBean (FeignClientFactoryBean является подклассом FactoryBean, который играет роль, когда beanDefinition генерирует beanInstance). Следующий код

private void registerFeignClient(BeanDefinitionRegistry registry,
		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    ···
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

Теперь .class компонентов, связанных с feign, преобразуется в BeanDefinition, а BeanDefinition помещается в контейнер spring beanFactory, а именно в свойство DefaultListableBeanFactroy.beanDefinitionMap, этот этап завершен. Мы заканчиваем этот этап экземпляром: BrandFeignClient помечен @FeignClient, поэтому, когда класс BrandFeignClient преобразуется в BeanDefinition. Значение beanClass сгенерированного BeanDefinition — FeignClientFactoryBean.class, а beanName BeanDefinition — полное имя BrandFeignClient. beanName хранится в beanFactory.beanDefinitionNames, в то же время карта с beanName в качестве ключа и BeanDefinition в качестве значения хранится в beanFactory.beanDefinitionMap. Когда необходимо получить BeanDefinition, контейнер beanFactory использует beanName в качестве ключа для получения соответствующего BeanDefinition из свойства beanDefinitionMap.

BeanDefinition компонентов, связанных с фикцией, преобразуется в BeanInstance.

Если AbstractApplicationContext.invokeBeanFactoryPostProcessors() отвечает за преобразование из загрузки .class в BeanDefinition, то AbstractApplicationContext.registerBeanPostProcessors() отвечает за преобразование из BeanDefinition в BeanInstance. Это только доказывает, что яРазница между BeanFactoryPostProcessory и BeanPostProcessorкак указано. Преобразование beanDefinition в beanInstance является общей логикой весенней загрузки. Подробнее см.Springboot beanDefinition преобразуется в анализ исходного кода процесса beanInstance и точки расширения.. Общая логика заключается в том, чтобы извлечь BeanDefinition из свойств beanDefinitionNames и beanDefinitionMap в контейнере beanFactory для создания экземпляра, присвоить значения свойств и т. д., чтобы сгенерировать beanInstance, соответствующий BeanDefinition.beanClass, а затем поместить его в DefaultSingletonBeanRegistry (родительский класс beanFactory). Свойство .singletonObjects. При последующем использовании beanInstance получается из этого свойства в соответствии с ключом

Преобразование в beanInstance через beanDefinition является общей логикой, как показано ниже.20191105185059.pngНа рисунке doCreateBean начинает отличаться, потому что есть два типа bean-компонентов в spring: FactoryBean и Bean; если это FactoryBean, beanInstance будет получен из AbstractAutowiredCapableBeanFactory.factoryBeanInstanceCache по beanName, а затем передан в populateBean( ) способ обработки? , разумеется, если он не получен, то также соблюдается логика типа Bean, то есть, если это Bean, будет вызван createBeanInstance(beanName, mbd, ··) для получения beanInstance путем разбора mbd (типа BeanDefinition ), а затем beanInstance будет храниться в DefaultSingletonBeanRegistry (родительский класс beanFactory ).singletonObjects. Очевидно, что beanClass класса BeanDifinition, идентифицированного @FeignClient, имеет тип FactoryBean (FeignClientFactoryBean), поэтому он следует логике FactoryBean. Мы напрямую ориентируемся на код ключа конверсии

AbstractAutowiredCapableBeanFactory class
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)  {
    Object beanInstance = doCreateBean(beanName, mbd, args); // (1)
    return beanInstance;
}

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args){
    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
}

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    return instantiateBean(beanName, mbd);
}

protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
    BeanWrapper bw = new BeanWrapperImpl(beanInstance);
    initBeanWrapper(bw);
    return bw;
}

SimpleInstantiationStrategy class
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    final Class<?> clazz = bd.getBeanClass();
    Constructor<?> constructorToUse = clazz.getDeclaredConstructor();
    return BeanUtils.instantiateClass(constructorToUse);
}

Приведенные выше методы показывают примерный процесс генерации beanInstance. Обратите особое внимание, вот точки расширения в нашей фактической разработке: Класс Class генерирует экземпляр T

第一步:得到T.class的Class clazz对象 --> clazz=T.class
第二步:获取clazz的构造函数 --> constructorToUse=clazz.getDeclaredConstructor()
第三步:生成instance --> BeanUtils.instantiateClass(constructorToUse)

Теперь BeanDefintion преобразуется в BeanInstance. Если взять в качестве примера BrandFeignClient, BeanDefintion (класс BrandFeignClient) преобразуется в объект BrandFeignClient, а объект BrandFeignClient сохраняется как значение (ключ — полное имя BrandFeignClient) в DefaultSingletonBeanRegistry (родительский класс beanFactory).singletonObjects

BeanInstance компонентов, связанных с фикцией, преобразуется в прокси-класс.

При создании экземпляра класса, который ссылается на класс @FeignClient, будет внедрен класс @FeignClient.В это время прокси-класс класса @FeignClient будет сгенерирован через прокси-сервер, а затем назначен созданному классу. Взяв пример кода, с которого мы начали, при создании экземпляра класса BrandController его переменной-члену BrandFeignClient также будет присвоено значение, и это значение генерируется beanInstance, объектом FeignClientFactoryBean, упомянутым выше, для создания прокси-класса прокси, так что контроллер может вызвать удаленный интерфейс RPC. Мы сосредоточимся на этом процессе, который также является общей реализацией технологии RPC.

Поговорим о том, как получить объект FeignClientFactoryBean, код выглядит следующим образом

AbstractBeanFactory class
protected <T> T doGetBean(final String beanName) throws BeansException {
    Object sharedInstance = getSingleton(beanName);
    Object bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

DefaultSingletonBeanRegistry class
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
	return singletonObject;
}

FactoryBeanRegistrySupport class
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    object = doGetObjectFromFactoryBean(factory, beanName);
	return object;
}
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) {
    object = factory.getObject();
}

FeignClientFactoryBean class
@Override
public Object getObject() throws Exception {
    return getTarget();
}

FeignClientFactoryBean class
<T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class); //(1)
    Feign.Builder builder = feign(context); //(2)

    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        return (T) loadBalance(builder, context,
                new HardCodedTarget<>(this.type, this.name, this.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 load 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); //(3)
    return (T) targeter.target(this, builder, context,
            new HardCodedTarget<>(this.type, this.name, url)); //(4)
}   

Перейдите к методу FeignClientFactoryBean.getObject(), в этом методе находится процесс генерации прокси-класса для класса @FeignClient. Эта логика разделена на четыре шага:

1. 获取FeignContext对象
2. 获取Feign.Builder对象
3. 获取Targeter对象
4. 调用Targeter.targeter()生成proxy代理类

В частности, после шага 2 есть очень важный логический момент: url будет использоваться как ветка, если url нет, то он пойдет на балансировку нагрузки, и наоборот. Значение и ценность этой строки в том, что мы можем использовать симуляцию удаленных вызовов двумя способами: во-первых, напрямую передавать значение атрибута url.域名调用http-интерфейс, второй — вызов http-интерфейса через балансировку нагрузки через Eureka. Метод URL-адреса можно использовать для обеспечения быстрых вызовов, не полагаясь на такие службы, как eureka, и может напрямую обращаться к целевой машине, что особенно подходит для сценариев быстрой итерации; а метод балансировки нагрузки обладает хорошей масштабируемостью и подходит для онлайн-сред. .

Для шагов создания прокси-класса для класса @FeignClient мы подробно опишем каждый шаг следующим образом.

Класс @FeignClient генерирует прокси-класс

Получить объект FeignContext

Как показано в методе FeignClientFactoryBean.getTarget() (1), объект FeignContext получается из beanFactory. И следующий код: FeignContext объявляется в виде метода @Bean.

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

Процесс генерации объектов FeignContext см. в разделе . Объект FeignContext содержит20191106171742.pngСвойство конфигурации на рисунке хранит все классы @FeignClient, FeignClientSpecification, для

Получить объект Feign.Builder

В методе FeignClientFactoryBean.getTarget() (2) объект Feign.Builder получается вызовом метода feign() Код выглядит следующим образом

protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);

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

    configureFeign(context, builder);

    return builder;
}

protected void configureFeign(FeignContext context, Feign.Builder builder) {
    FeignClientProperties properties = this.applicationContext
            .getBean(FeignClientProperties.class);
    if (properties != null) {
        if (properties.isDefaultToProperties()) {
            configureUsingConfiguration(context, builder);
            configureUsingProperties(
                    properties.getConfig().get(properties.getDefaultConfig()),
                    builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                    builder);
        }
        else {
            configureUsingProperties(
                    properties.getConfig().get(properties.getDefaultConfig()),
                    builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                    builder);
            configureUsingConfiguration(context, builder);
        }
    }
    else {
        configureUsingConfiguration(context, builder);
    }
}

Как показано в приведенном выше коде, объект создается путем присвоения значений атрибутам объекта Feign.Builder.Эти атрибуты включают requestInterceptors, logLevel, contract, client, encoder, decoder, queryMapEncoder, options и т. д. Объект Builder отвечает за генерацию прокси-класса класса @FeignClient, поэтому эти свойства будут использоваться при генерации прокси-прокси. Метод configureFeign() иллюстрирует логику: есть два способа настроить значение свойства @FeignClient: один — это конфигурация файла properties.yml, а другой — комбинация @Configuration и @Bean. И первый метод переопределяет второй метод по умолчанию, но его можно отменить, настроив значение свойства feign.client.defaultToProperties.

Получить объект Targeter
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
        return new HystrixTargeter();
    }
}

Генерация объекта Targeter аналогична процессу генерации объекта FeignContext, см. . Функция этого объекта — определить, следует ли настраивать Hystrix для резервного слияния.

Вызовите Targeter.targeter() для создания прокси-класса

Прокси-класс генерируется в методе FeignClientFactoryBean.getTarget() (4). код показывает, как показано ниже

HystrixTargeter class
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
        FeignContext context, Target.HardCodedTarget<T> target) {
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        return feign.target(target);
    }
}

Feign.Builder class
public <T> T target(Target<T> target) {
    return build().newInstance(target);
}
    
public Feign build() {
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
            logLevel, decode404, closeAfterDecode, propagationPolicy);
    ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
            errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

ReflectiveFeign class
public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, 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 {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    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;
}

В этот момент я наконец вижу генерацию прокси. ReflectiveFeign непосредственно отвечает за создание прокси-классов. Как видно из newInstance(), генерация прокси использует динамический прокси jdk. В этом процессе особенно важны два класса: FeignInvocationHandler и SynchronousMethodHandler. SynchronousMethodHandler является подклассом MethodHandler.Функция видна из названия.Обработчик-обработчик, соответствующий методу метода, хранит в себе хранилище результатов разбора каждого метода в классе, который идентифицирует @FeignClient, как показано на рисунке:20191107074325.png

FeignInvocationHandler является подклассом jdk InvocationHandler, то есть вызывает через него прокси-класс, создание класса FeignInvocationHandler происходит в виде фабричного метода, который стоит изучить. При создании прокси-класса FeignInvocationHandler заключает в оболочку коллекцию SynchronousMethodHandler и передает ее в прокси-класс. Сгенерированный прокси-класс показан на рисунке.20191107080016.png

BeanPostProcessors()) { Object current = processor.postProcessAfterInitialization

AutowiredAnnotitionBeanPostProcessor class
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) {
    // 赋权限filed可见性,防止filed是private时导致赋值报错
    ReflectionUtils.makeAccessible(field);
    // 反射给属性赋值
    field.set(bean, value);
}

		

Значения field.set(bean, value) показаны на рисунке ниже подано:20191107081652.png bean: BrandController

value: 20191107081510.pngТаким образом, назначение свойства BrandController.brandFeignClient на данный момент завершено. То есть BrandController.brandFeignClient=$Proxy105, поэтому, когда HTTP-запрос поступает в метод Controller, вызывается метод brandFeignClient для входа в логику класса прокси.

Анализ обработки запроса

Создан экземпляр компонента Feign и назначен BrandController.brandFeignClient. Теперь его можно применять, когда приходит http запрос, запускается его логика

послать запрос:

curl -X GET 'http://127.0.0.1:6003/brand/query?id=1&code=2'

Соответствующий метод обработки запроса:20191107085101.png

Можно видеть, что значение brandFeignClient в это время равноProxy105, то есть, как показано на рисунке ниже, метод напрямую входитFeignInvocationHandler.invokeметод(Если вы сомневаетесь здесь, см.Proxy105, как показано на рисунке ниже, метод напрямую входит в метод FeignInvocationHandler.invoke (если у вас есть какие-либо вопросы здесь, см..class файл proxy105).20191107085102.png

Вот на чем мы сосредоточимся.Логика здесь общая, и каждый прокси-класс Feign следует этой логике. Помните класс FeignInvocationHandler, мы сосредоточились на создании раздела прокси-класса, он был создан в то время, и теперь он начинает использовать значения атрибутов во время создания экземпляра. см. код

FeignInvocationHandler class
public Object invoke(Object proxy, Method method, Object[] args) {
    return dispatch.get(method).invoke(args);
}

dispatch:Map тип, значение SynchronousMethodHandler, SynchronousMethodHandler хранит информацию о каждом методе. отправка, как показано ниже20191107090336.png

см. код

SynchronousMethodHandler class
public Object invoke(Object[] argv) throws Throwable {
    // 将BrandFeignClient.query()方法转化为包含http请求信息RequestTemplate对象,如下图
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            return executeAndDecode(template);
        } catch (RetryableException e) {
           // 重试逻辑
        }
    }
}

способ в два шага первый шаг: Преобразуйте метод класса @FeignClient в объект RequestTemplate, содержащий информацию о HTTP-запросе, как показано на рисунке.20191108073006.png

Шаг 2: Соберите и отправьте запрос запроса, обработайте данные ответа ответа

  Object executeAndDecode(RequestTemplate template) throws Throwable {
    // 组装成request请求,
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response = client.execute(request, options);
}

Метод сначала будет собран в запрос запроса, который будет перехвачен и обработан RequestInterceptor, что дает нам возможность расширения. Здесь мы можем наследовать RequestInterceptor для реализации нашей логики в фактической разработке, например, для обработки данных тела запроса метода Get. Затем журнал записывается в соответствии с уровнем журнала.Вдохновение здесь заключается в том, что мы можем инкапсулировать и обрабатывать журнал в нашей обычной разработке.

Последним является использование LoadBalanceFeignClient (это FeignClient, используемый feign по умолчанию), который сочетает ленту и eureka для достижения балансировки нагрузки, находит удаленную службу в соответствии с алгоритмом ленты и, наконец, отправляет запрос для получения данных ответа. Для балансировки нагрузки на ленту см. онлайн-документацию.

На этом загрузка и парсинг всего компонента feign и обработка запроса завершены, на то, чтобы избежать пропусков, ушло 3 дня.

Сводка процесса создания экземпляра класса, отмеченного @FeignClient

Процесс преобразования файла java.class при весенней загрузке .class-->BeanDefinition-->beanInstance-->прокси класс прокси

1. beanDefinition在beanFactory的存储位置:
DefaultListableBeanFactroy.beanDenifitionNames.add(beanName);
DefaultListableBeanFactroy.beanDenifitionMap.put(beanName, BeanDefinition);
beanName:com.skyler.api.TestStoppageFeignService
BeanDefinition:GenericBeanDefinition(beanClass=class org.springframework.cloud.openfeign.FeignClientFactoryBean))


2. beanInstance在beanFactory的存储位置:
DefaultSingletonBeanRegistry.singletonObjects.put(beanName, beanInstance);
beanName:com.com.skyler.api.TestStoppageFeignService
beanInstance:FeignClientFactoryBean(type=interface com.skyler.api.TestStoppageFeignService)

3. proxy代理类在beanFactory的存储位置:
不会放入beanFactory,而是直接赋值给引用它的属性

Чтобы узнать об использовании притворства, см.

https://segmentfault.com/a/1190000020656405
https://juejin.cn/post/6844903781423923214