当前位置: 首页 > news >正文

【Spring-cloud-OpenFegin源码解析】

文章目录

  • OpenFegin介绍
  • OpenFegin的特性
  • OpenFegin的源码流程
  • OpenFegin源码分析

OpenFegin介绍

Feign是一个声明式 Web 服务客户端。它简化了 Web 服务客户端的编写。要使用 Feign,只需创建一个接口并对其进行注解即可。它支持可插入式注解,包括 Feign 注解和 JAX-RS 注解。Feign 还支持可插入式编码器和解码器。Spring Cloud 增加了对 Spring MVC 注解的支持,并支持使用HttpMessage ConvertersSpring Web 中默认使用的注解。Spring Cloud 集成了 Eureka、Spring Cloud CircuitBreaker 以及 Spring Cloud LoadBalancer,以便在使用 Feign 时提供负载均衡的 http 客户端。

OpenFegin的特性

  1. 声明式调用 使用 Java 接口 + 注解定义远程调用
  2. 支持 Spring MVC 注解 @RequestMapping、@GetMapping 等
  3. 支持参数注解 @RequestParam、@PathVariable、@RequestHeader 等
  4. 支持负载均衡 内部可集成 Ribbon / Spring Cloud LoadBalancer
  5. 支持熔断降级 可集成 Sentinel / Resilience4j 等
  6. 支持拦截器 请求日志、Token 注入、自定义 Header
  7. 支持全局配置 自定义超时时间、编码器、解码器、日志等级等

OpenFegin的源码流程

添加依赖(Spring Boot 项目)

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

启用openFegin功能

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

声明接口:

@FeignClient(name="storeClient")
public interface StoreClient {@RequestMapping(method = RequestMethod.GET, value = "/stores")List<Store> getStores();@RequestMapping(method = RequestMethod.GET, value = "/stores")Page<Store> getStores(Pageable pageable);@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")Store update(@PathVariable("storeId") Long storeId, Store store);@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")void delete(@PathVariable Long storeId);
}

调用

class TestFegin{@Autowiredprivate StoreClient storeClient;@RequestMapping("/query")public List<Store> query(){return storeClient.getStore();}
}

配置

feign:client:config:default:connectTimeout: 5000readTimeout: 10000loggerLevel: FULLretryer:maxAttempts: 3httpclient:enabled: true

OpenFegin源码分析

源码下载:
1.下载版本Openfegin-2.1.3.RELEASE
2. 下载地址:https://github.com/spring-cloud/spring-cloud-openfeign/tree/2.1.x

源码分析:
在这里插入图片描述

分析源码入口:
开启Fegin的开关类,EnableFeginClient

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {/*** Alias for the {@link #basePackages()} attribute. Allows for more concise annotation* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of* {@code @ComponentScan(basePackages="org.my.pkg")}.* @return the array of 'basePackages'.*/String[] value() default {};/*** Base packages to scan for annotated components.* <p>* {@link #value()} is an alias for (and mutually exclusive with) this attribute.* <p>* Use {@link #basePackageClasses()} for a type-safe alternative to String-based* package names.* @return the array of 'basePackages'.*/String[] basePackages() default {};/*** Type-safe alternative to {@link #basePackages()} for specifying the packages to* scan for annotated components. The package of each class specified will be scanned.* <p>* Consider creating a special no-op marker class or interface in each package that* serves no purpose other than being referenced by this attribute.* @return the array of 'basePackageClasses'.*/Class<?>[] basePackageClasses() default {};/*** A custom <code>@Configuration</code> for all feign clients. Can contain override* <code>@Bean</code> definition for the pieces that make up the client, for instance* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.** @see FeignClientsConfiguration for the defaults* @return list of default configurations*/Class<?>[] defaultConfiguration() default {};/*** List of classes annotated with @FeignClient. If not empty, disables classpath* scanning.* @return list of FeignClient classes*/Class<?>[] clients() default {};}

核心FeignClientsRegistrar,将当前的被FeginClient注解修饰的类,注册到BeanFactory中。

/** Copyright 2013-2019 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.cloud.openfeign;import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;/*** @author Spencer Gibb* @author Jakub Narloch* @author Venil Noronha* @author Gang Li*/
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {// patterned after Spring Integration IntegrationComponentScanRegistrar// and RibbonClientsConfigurationRegistgrarprivate ResourceLoader resourceLoader;private Environment environment;FeignClientsRegistrar() {}static void validateFallback(final Class clazz) {Assert.isTrue(!clazz.isInterface(),"Fallback class must implement the interface annotated by @FeignClient");}static void validateFallbackFactory(final Class clazz) {Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "+ "of fallback classes that implement the interface annotated by @FeignClient");}static String getName(String name) {if (!StringUtils.hasText(name)) {return "";}String host = null;try {String url;if (!name.startsWith("http://") && !name.startsWith("https://")) {url = "http://" + name;}else {url = name;}host = new URI(url).getHost();}catch (URISyntaxException e) {}Assert.state(host != null, "Service id not legal hostname (" + name + ")");return name;}static String getUrl(String url) {if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {if (!url.contains("://")) {url = "http://" + url;}try {new URL(url);}catch (MalformedURLException e) {throw new IllegalArgumentException(url + " is malformed", e);}}return url;}static String getPath(String path) {if (StringUtils.hasText(path)) {path = path.trim();if (!path.startsWith("/")) {path = "/" + path;}if (path.endsWith("/")) {path = path.substring(0, path.length() - 1);}}return path;}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry);}private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {String name;if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));}}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");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() {@Overrideprotected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}for (String basePackage : basePackages) {Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name,attributes.get("configuration"));registerFeignClient(registry, annotationMetadata, attributes);}}}}private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}private void validate(Map<String, Object> attributes) {AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);// This blows up if an aliased property is overspecified// FIXME annotation.getAliasedString("name", FeignClient.class, null);validateFallback(annotation.getClass("fallback"));validateFallbackFactory(annotation.getClass("fallbackFactory"));}/* for testing */ String getName(Map<String, Object> attributes) {String name = (String) attributes.get("serviceId");if (!StringUtils.hasText(name)) {name = (String) attributes.get("name");}if (!StringUtils.hasText(name)) {name = (String) attributes.get("value");}name = resolve(name);return getName(name);}private String getContextId(Map<String, Object> attributes) {String contextId = (String) attributes.get("contextId");if (!StringUtils.hasText(contextId)) {return getName(attributes);}contextId = resolve(contextId);return getName(contextId);}private String resolve(String value) {if (StringUtils.hasText(value)) {return this.environment.resolvePlaceholders(value);}return value;}private String getUrl(Map<String, Object> attributes) {String url = resolve((String) attributes.get("url"));return getUrl(url);}private String getPath(Map<String, Object> attributes) {String path = resolve((String) attributes.get("path"));return getPath(path);}protected ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, this.environment) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate = false;if (beanDefinition.getMetadata().isIndependent()) {if (!beanDefinition.getMetadata().isAnnotation()) {isCandidate = true;}}return isCandidate;}};}protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());Set<String> basePackages = new HashSet<>();for (String pkg : (String[]) attributes.get("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : (String[]) attributes.get("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));}return basePackages;}private String getQualifier(Map<String, Object> client) {if (client == null) {return null;}String qualifier = (String) client.get("qualifier");if (StringUtils.hasText(qualifier)) {return qualifier;}return null;}private String getClientName(Map<String, Object> client) {if (client == null) {return null;}String value = (String) client.get("contextId");if (!StringUtils.hasText(value)) {value = (String) client.get("value");}if (!StringUtils.hasText(value)) {value = (String) client.get("name");}if (!StringUtils.hasText(value)) {value = (String) client.get("serviceId");}if (StringUtils.hasText(value)) {return value;}throw new IllegalStateException("Either 'name' or 'value' must be provided in @"+ FeignClient.class.getSimpleName());}private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());}@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}/*** Helper class to create a {@link TypeFilter} that matches if all the delegates* match.** @author Oliver Gierke*/private static class AllTypeFilter implements TypeFilter {private final List<TypeFilter> delegates;/*** Creates a new {@link AllTypeFilter} to match if all the given delegates match.* @param delegates must not be {@literal null}.*/AllTypeFilter(List<TypeFilter> delegates) {Assert.notNull(delegates, "This argument is required, it must not be null");this.delegates = delegates;}@Overridepublic boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {for (TypeFilter filter : this.delegates) {if (!filter.match(metadataReader, metadataReaderFactory)) {return false;}}return true;}}}
http://www.dtcms.com/a/304618.html

相关文章:

  • VitePress学习笔记
  • 编程算法在金融、医疗、教育、制造业的落地应用。
  • 云服务器上基于lora微调Qwen2.5-VL-7b-Instruct模型之Lora微调代码讲解
  • Netty中InternalThreadLocalMap的作用
  • Rust实现GPU驱动的2D渲染引擎
  • Vue3 学习教程,从入门到精通, Vue3 自定义指令语法知识点及案例(20)
  • c++ nlohmann/json读写json文件
  • JavaWeb学习打卡18(JDBC案例详解)
  • ansible 使用更高版本的python版本
  • Python中的决策树机器学习模型简要介绍和代码示例(基于sklearn)
  • 【牛客网C语言刷题合集】(五)——主要二进制、操作符部分
  • GO 开发环境安装及配置
  • Claude Code 使用教程(对接智谱模型)
  • 84、【OS】【Nuttx】【启动】栈溢出保护:asm 关键字(下)
  • SpringBoot集成Quzrtz实现定时任务
  • 【目标检测】小样本度量学习
  • 记录一个TI DSP编译器的Bug
  • CentOS安装ffmpeg并转码视频为mp4
  • 预过滤环境光贴图制作教程:第四阶段 - Lambert 无权重预过滤(Stage 3)
  • 预过滤环境光贴图制作教程:第一步 - HDR 转立方体贴图
  • Android Compose 自定义组件完全指南
  • 对College数据进行多模型预测(R语言)
  • 《React与Vue构建TODO应用的深层逻辑》
  • 【lucene】SegmentCoreReaders
  • linux_前台,后台进程
  • LeetCode热题100——155. 最小栈
  • (LeetCode 面试经典 150 题) 150. 逆波兰表达式求值 (栈)
  • 电脑主机显示的python版本是3.8.6,但是我在控制面板没有找到,想删除不知道怎么操作怎么办
  • 《 java 随想录》| LeetCode链表高频考题
  • 【LeetCode】大厂面试算法真题回忆(111)--身高排序