задний план
Среди технических решений для удаленного вызова 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组件,解决其他的问题
Пример
Ниже приведен простейший пример использования имитации.分别定义一个启动类、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 этот метод разделен на два этапа.
- Сначала определите каталог пакета для поиска
- Во-вторых, получите и проанализируйте классы @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 является общей логикой, как показано ниже.На рисунке 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 см. в разделе
Получить объект 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, см.
Вызовите 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, как показано на рисунке:
FeignInvocationHandler является подклассом jdk InvocationHandler, то есть вызывает через него прокси-класс, создание класса FeignInvocationHandler происходит в виде фабричного метода, который стоит изучить. При создании прокси-класса FeignInvocationHandler заключает в оболочку коллекцию SynchronousMethodHandler и передает ее в прокси-класс. Сгенерированный прокси-класс показан на рисунке.
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) показаны на рисунке ниже подано: bean: BrandController
value: Таким образом, назначение свойства 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'
Соответствующий метод обработки запроса:
Можно видеть, что значение brandFeignClient в это время равно.class файл proxy105).
Вот на чем мы сосредоточимся.Логика здесь общая, и каждый прокси-класс Feign следует этой логике. Помните класс FeignInvocationHandler, мы сосредоточились на создании раздела прокси-класса, он был создан в то время, и теперь он начинает использовать значения атрибутов во время создания экземпляра. см. код
FeignInvocationHandler class
public Object invoke(Object proxy, Method method, Object[] args) {
return dispatch.get(method).invoke(args);
}
dispatch:Map
см. код
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-запросе, как показано на рисунке.
Шаг 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