Проведите выходные, изучая основные принципы OpenFeign |

Архитектура

предисловие

Сегодняшние микросервисы широко используются в интернет-кружке, а SpringCloud — заслуженная «топ-карта» в сфере микросервисов.

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

Почему стоит выбрать OpenFien?Потому что он достаточно «маленький», чтобы соответствовать нашему названию:Сделай это за выходные

В исходном коде Feign длина кода Java составляет всего 30 000. Глядя на популярные проекты с открытым исходным кодом, включая, помимо прочего, Dubbo, Naocs и Skywalking, код Java должен начинаться с 300 000 строк.

Я надеюсь, что благодаря этой статье читатели и друзья смогут овладеть следующими знаниями.

  • Что такое притворяться
  • Разница между Фейном и Опенфейном
  • Стартовый принцип OpenFeign
  • Как работает OpenFeign
  • Как балансирует нагрузку OpenFeign

версия spring-cloud-starter-openfeign: 2.2.6.RELEASE

Что такое притворяться

Feign — это декларативный клиент веб-сервиса, упрощающий написание клиентов веб-сервиса.

Feign не выполняет никакой обработки запроса, генерирует запрос, обрабатывая информацию, связанную с аннотацией, и декодирует данные, возвращенные вызовом, для достиженияУпрощение разработки HTTP API

Если вы хотите использовать Feign, вам нужно создать интерфейс и добавить в него знания, связанные с Feign, и FeignТакже поддерживает подключаемые кодеры и декодеры., посвященный созданию облегченного HTTP-клиента

Разница между Фейном и Опенфейном

Feign был впервые созданподдерживается Netflix, позже Netflix больше не поддерживает его, и в конце концовFeign поддерживается сообществом, переименованный в Openfeign

Во избежание ввода двух слов, в дальнейшем именуемых Opefeign как Feign

И переносим исходный проект на новый склад, поэтому мы видим координаты Feign на Github следующим образом

<groupId>io.github.openfeign</groupId>
<artifactId>parent</artifactId>
<version>...</version>

Starter Openfeign

Конечно, основываясь на любви команды SpringCloud к Netflix, вы разработали такой полезный легкий HTTP-клиент Мой старший брат не может его поддерживать, поэтому есть стартовый пакет на основе Feign.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Spring Cloud добавляет поддержку аннотаций Spring MVC и поддерживает использование тех же HttpMessageConverters, которые используются по умолчанию в Spring Web.

Кроме того, Spring Cloud Big Brother интегрирует Ribbon и Eureka, а также Spring Cloud LoadBalancer для предоставления HTTP-клиента с балансировкой нагрузки при использовании Feign.

Поддержка реестров, включая, помимо прочего, Eureka, таких как Consul, Naocs и другие реестры.

В процессе разработки нашего проекта SpringCloud мы в основном используем этот Starter Feign.

Подготовка окружающей среды

Для облегчения понимания здесь приведены соответствующие демо-коды производителя и потребителя, а также используемый центр регистрации.

Naco, используемые реестром, имеют относительно простые коды производства и потребления. Кроме того, для опыта чтения,Принцип статьи - ставить меньше исходников, больше, чтобы разобраться в основной логике для вас

сервис продюсера

Добавьте аннотацию обнаружения регистрации службы Nacos и опубликуйте службу интерфейса HTTP.

@EnableDiscoveryClient @SpringBootApplication
public class NacosProduceApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosProduceApplication.class, args);
    }
    @RestController
    static class TestController {
        @GetMapping("/hello")
        public String hello(@RequestParam("name") String name) {
            return "hello " + name;
        }
    }
}

Потребительские услуги

Определить интерфейс потребительской службы FeignClient

@FeignClient(value = "nacos-produce")
public interface DemoFeignClient {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String sayHello(@RequestParam("name") String name);
}

Поскольку производители используют Nacos, потребители не только включают аннотации Feign, но и позволяют обнаруживать регистрацию службы Naocs.

@RestController @EnableFeignClients
@EnableDiscoveryClient @SpringBootApplication
public class NacosConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosConsumeApplication.class, args);
    }

    @Autowired private DemoFeignClient demoFeignClient;

    @GetMapping("/test")
    public String test() {
        String result = demoFeignClient.sayHello("公号-源码兴趣圈");
        return result;
    }
}

Исходный принцип Feign

Во время использования SpringCloud, если мы хотим запустить компонент, мы обычно делаем@Enable...Внедренный таким образом Feign не является исключением, нам нужно пометить эту аннотацию на классе@EnableFeignClients

@EnableFeignClients
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Продолжайте копать глубже, чтобы увидеть, что происходит внутри аннотации. Внутренний метод аннотации не будет объясняться. Будет конфигурация по умолчанию без добавления. Если вам интересно, вы можете следить за исходным кодом.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...}

Первые три аннотации выглядят ничем не примечательными, а основное внимание уделяется четвертой @Import, Обычно эта аннотация используется для динамической регистрации Spring Beans.

Внедрить @Import

Его также можно приблизительно догадаться по имени. Оно используется Feign для регистрации компонента. Он использует интерфейс, связанный со Spring. Давайте посмотрим, что он делает.

ResourceLoaderAware и EnvironmentAware — это два свойства в FeignClientsRegistrar.ResourceLoader, средаЗадание, это не большая проблема, чтобы понять маленьких партнеров, которые понимают Spring

ImportBeanDefinitionRegistrar отвечает за динамическое внедрение компонентов IOC, соответственно за внедрение классов конфигурации Feign и компонентов FeignClient.

// 资源加载器,可以加载 classpath 下的所有文件
private ResourceLoader resourceLoader;
// 上下文,可通过该环境获取当前应用配置属性等
private Environment environment;

@Override
public void setEnvironment(Environment environment) {
    this.environment = environment;
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
}

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  	// 注册 @EnableFeignClients 提供的自定义配置类中的相关 Bean 实例
    registerDefaultConfiguration(metadata,registry);
    // 扫描 packge,注册被 @FeignClient 修饰的接口类为 IOC Bean
    registerFeignClients(metadata, registry);
}

Добавить глобальную конфигурацию

Процесс метода registerDefaultConfiguration выглядит следующим образом.

  1. Получите свойства аннотации @EnableFeignClients и соответствующее значение.
  2. генерироватьFeignClientSpecification(Сохраняет класс конфигурации в Feign) Соответствующий конструктор BeanDefinitionBuilder
  3. FeignClientSpecification Имя компонента по умолчанию + @EnableFeignClients полное имя оформленного класса + FeignClientSpecification
  4. @EnableFeignClients defaultConfiguration по умолчанию имеет значение {}, если нет связанной конфигурации,默认使用 FeignClientsConfigurationИ заполните FeignClientSpecification именем и, наконец, зарегистрируйтесь как IOC Bean

Зарегистрировать интерфейс FeignClient

Сосредоточьтесь на registerFeignClients, этот метод в основном предназначен для регистрации интерфейса, украшенного @FeignClient, в качестве компонента IOC.

  1. Просканировать аннотацию @EnableFeignClients, если есть клиенты, загрузить указанный интерфейс, если он пустой, просканировать интерфейс, украшенный @FeignClient, согласно правилам сканера
  2. Получите соответствующие атрибуты в @FeignClient и создайте атрибуты уровня интерфейса в соответствии с атрибутами конфигурации.FeignClientSpecificationКласс конфигурации IOC Bean
  3. Установите для свойства @FeignClient значениеFeignClientFactoryBeanобъект и зарегистрируйте IOC Bean

Интерфейс, декорированный @FengnClient, фактически использует прокси-фабрику Spring для генерации прокси-классов, поэтому здесь BeanDefinition, оформленный с помощью интерфейса @FeignClient, будет иметь тип FeignClientFactoryBean, иFeignClientFactoryBean наследуется от FactoryBean

То есть, когда мы определяем оформленный интерфейс @FeignClient, тип компонента, зарегистрированный в контейнере IOC, становится FeignClientFactoryBean.

В Spring FactoryBean — это фабричный компонент, используемый для создания прокси-компонентов.Фабричный боб — это особый вид бобов., потребитель, которому нужно получить боб, не знает, является ли этот компонент обычным компонентом или заводским компонентом.Экземпляр, возвращаемый фабричным компонентом, не является самим фабричным компонентом., но вернет выполнение фабричного бинаFactoryBean#getObjectпример логики

Как работает фейк

Говоря о принципе работы Feign, основной момент вращается вокруг интерфейса, украшенного @FeignClient, как отправлять и получать сетевые HTTP-запросы.

Как упоминалось выше, тип интерфейса, декорированный @FeignClient, наконец-то заливается в контейнер IOC.Тип FeignClientFactoryBean — FeignClientFactoryBean. Давайте посмотрим, что это

Характеристики интерфейса FactoryBean

Вот характеристики FeignClientFactoryBean

  1. Согласно Spring, он выполняет часть логики при инициализации класса.InitializingBeanинтерфейс
  2. Если он будет введен другим классом @Autowired, он не вернет себя, аFactoryBean#getObjectВозвращенный класс, согласно SpringFactoryBeanинтерфейс
  3. Он может получить объект контекста Spring, согласно SpringApplicationContextAwareинтерфейс

Давайте сначала посмотрим, что делает его логика инициализации.

@Override
public void afterPropertiesSet() {
    Assert.hasText(contextId, "Context id must be set");
    Assert.hasText(name, "Name must be set");
}

Нет никакой специальной операции, просто используйте класс инструмента утверждения, чтобы определить, что два поля не пусты. ApplicationContextAware нечего сказать.Получите объект контекста и присвойте его локальной переменной объекта.Ключ и ключFactoryBean#getObjectметод

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

Метод исходного кода getTarget все еще довольно длинный, и он отображается в виде сегментов.

<T> T getTarget() {
  	// 从 IOC 容器获取 FeignContext
    FeignContext context = applicationContext.getBean(FeignContext.class);
  	// 通过 context 创建 Feign 构造器
    Feign.Builder builder = feign(context);
		...
}

Вопрос здесь? Когда и где FeignContext внедряется в контейнер Spring?

Когда увидите картинку, поймете.Как можно не использовать функцию автоматической сборки при использовании SpringBoot?FeignContext успешно создается в FeignAutoConfiguration

Инициализировать родительско-дочерний контейнер

В методе feign фабрика логов, кодирование, декодирование и другие классы получаются через метод get(...)

Это включает в себя концепцию родительско-дочернего контейнера Spring,Карта подконтейнера по умолчанию пуста, если Контекст, соответствующий названию службы, не может быть получен, создайте новый

Как видно из рисунка ниже, зарегистрированныйFeignClientsConfigurationТип Bean, кодировщик, декодер и другие компоненты, полученные в приведенном выше методе feign, по умолчанию получаются из этого класса.

Регистрация по умолчанию выглядит следующим образом: FeignClientsConfiguration передается путем создания FeignContext и вызова конструктора суперкласса Super.

Относительно соответствия между контейнерами родительского и дочернего классов и отношения между предоставлением сервисов @FeignClient, соответствующих субконтейнерам (каждый сервис соответствует экземпляру субконтейнера)

Возвращаясь к методу getInstance, подконтейнер загрузил соответствующий Bean в это время и получил его непосредственно через getBean.FeignLoggerFactory

Точно так же Feign.Builder, Encoder, Decoder и Contract могут получать соответствующие bean-компоненты через подконтейнеры.

Метод configureFeign в основном выполняет некоторые назначения конфигурации,Например, тайм-аут, повторная попытка, конфигурация 404 и т. д., я не буду вдаваться в подробности о коде присваивания.

Здесь необходимо обобщить первую половину кода создания фабрики прокси Spring

  1. При внедрении службы @FeignClient на самом деле вводитсяFactoryBean#getObjectВернуть объект прокси-фабрики
  2. Получить контекст FeignContext через контейнер IOC
  3. При создании объекта Feign.Builder создается подконтейнер, соответствующий сервису Feign.
  4. Получить фабрику журналов, кодировщик, декодер и другие компоненты из подконтейнеров
  5. Установите конфигурацию для Feign.Builder, такую ​​как время ожидания, уровень журнала и другие свойства, каждую службу можно настроить

Генерация динамического прокси

Продолжайте есть, закуски все на ней, следующее самое важное место, маленькая скамья стоит вертикально..

Поскольку мы используем имя вместо URL в аннотации @FeignClient, будет выполнена ветвь стратегии балансировки нагрузки.

Клиент: Feign отправляет запросы и получает ответы и т. д., завершается клиентом, этот класс по умолчанию имеет значение Client.Default и поддерживает HttpClient, OkHttp и другие клиенты.

Клиент и Таргетер в коде регистрируются во время автосборки.В соответствии с вышеизложенной теорией родительско-дочернего контейнера, эти два компонента существуют в родительском контейнере.

Поскольку мы не настраивали Hystix, поэтому перейдите в эту ветку

Создайте класс отражения ReflectiveFeign, а затем выполните его, чтобы создать класс экземпляра.

Метод newInstance анализирует и преобразует конфигурации, такие как SpringMvc, в интерфейс, оформленный @FeignClient, классифицирует методы в классе интерфейса и создает динамический прокси-класс.

Видно, что способ, которым Feign создает динамические прокси-классы, такой же, как и у Mybatis Mapper, потому что ни один из них не реализует классы.

Согласно методу newInstance, грубо разделенному по поведению, было сделано четыре вещи.

  1. Обработка аннотаций @FeignCLient (аннотаций SpringMvc и т. д.) инкапсулируется какMethodHandlerКласс упаковки
  2. Просмотрите все методы в интерфейсе, отфильтруйте методы объекта и классифицируйте методы по умолчанию и методы FeignClient.
  3. Создайте динамический прокси, соответствующийInvocationHandlerи создайте экземпляр прокси
  4. Метод по умолчанию в интерфейсеПривязать динамический прокси-класс

MethodHandler анализирует и сохраняет параметры метода, возвращаемые значения метода, наборы параметров, типы запросов и пути запросов.

На данный момент мы можем видеть, как работает Feign. Впереди так много инкапсуляций, инкапсуляция персонализированной конфигурации и т. д., что окончательное решение — создать динамический прокси-класс.

То есть, когда мы вызываем интерфейс @FeignClient, он будетFeignInvocationHandler#invokeПерехватите и выполните следующую логику в методе динамического прокси

  1. Информация аннотаций интерфейса инкапсулируется в виде HTTP-запроса.
  2. Получите список служб через ленту и выполните вызов балансировки нагрузки в списке служб (Имя службы конвертируется в ip+port)
  3. После вызова запроса возвращаемые данные инкапсулируются как HTTP-ответ, а затем преобразуются в возвращаемый тип в интерфейсе.

Теперь, когда вы поняли процесс вызова, давайте серьезно попробуем и посмотрим, есть ли...

RequestTemplate: создайте класс шаблона запроса.

Параметры: хранить классы конфигурации, такие как соединение и время ожидания.

Retryer: неудачный класс стратегии повторной попытки

Я много раз читал логику повторной попытки, но как на это посмотреть, немного избыточно помещать ключевое слово continue в конец while...

Используется при выполнении логики удаленного вызоваRxjava (реактивное программирование), вы можете увидеть способ преобразования имени службы в ip+port после получения сервера через нижний слой

Этот подход реактивного программирования очень распространен в SpringCloud.Также используется нижний слой исходного кода Hystix.

Сетевые вызовы используются по умолчаниюHttpURLConnection, можно настроить для использования HttpClient или OkHttp

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

图片参考@疯狂创客圈

Как Feign распределяет нагрузку

Вообще говоря, наши производители регистрируют несколько сервисов, и потребители должны использовать балансировку нагрузки, чтобы вызывать их изВыберите здоровую и доступную услугу производителя

Поскольку Feign интегрирует Ribbon внутри, он также поддерживает эту функцию, давайте посмотрим, как он это делает.

Мы зарегистрировали два сервиса на Nacos с номерами портов 8080 и 8081. Вы можете получить набор услуг, когда получите балансировщик нагрузки.

Затем выберите работоспособный экземпляр для возврата с помощью метода ChooseServer, и позже будет опубликована новая статья, в которой подробно объясняется балансировка нагрузки ленты.

Замените имя службы в URL-адресе возвращенным сервером и, наконец, используйте службу сетевых вызовов, чтобы сделать удаленный вызов, идеальное совпадение

Эпилог

Статья знакомит с тем, что такое Feign из самых базовых знаний? Затем основной принцип Feign объясняется с точки зрения исходного кода, который резюмируется следующим образом:

  1. Запустите компонент Feign Starter с помощью аннотации @EnableFeignCleints.
  2. Feign Starter регистрирует глобальную конфигурацию во время запуска проекта, сканирует все классы интерфейса @FeignClient в пакете и регистрирует контейнер IOC.
  3. Когда внедряется класс интерфейса @FeignClient, передайтеFactoryBean#getObjectВозвращает динамический прокси-класс
  4. Когда интерфейс вызывается, он перехватывается логикой динамического прокси-класса, и информация о запросе @FeignClient генерируется кодировщиком.
  5. Разрешить ленте выполнить балансировку нагрузки и выбрать работоспособный экземпляр сервера.
  6. Затем используйте клиент для переноса запроса на вызов удаленной службы для возврата ответа на запрос.
  7. Сгенерировать ответ через декодер и вернуть его клиенту, а также разобрать информационный поток на возвращаемые интерфейсом данные.

Хотя Feign относительно невелик, нереально хотеть, чтобы статья полностью описывала его, поэтому вот некоторые основные моменты, которые нужно объяснить, пожалуйста, простите меня за места, которые не написаны.

Кроме того, в связи с ограниченным уровнем автора, приветствуются отзывы и исправления ошибок в статье, спасибо

Поиск в Wechat [круг интереса к исходному коду], подпишитесь на официальный аккаунт и ответьте 123, чтобы получить учебные материалы, такие как GO, Netty, Seata, SpringCloud Alibaba, спецификации разработки, сборник интервью, структуру данных и так далее!


Справочная статья: