Feign调用原理
一、Springcloud-openfeign使用
示例:整合springboot项目的使用:版本对应关系
- 阿里官方对应版本:SpringBoot-SpringCloud-SpringCloudAlibaba各版本对应关系
- Spring官方:SpringBoot-SpringCloud版本对应关系,需格式化json串
- SpringCloud官方:点击对应的Reference文档,文档内有版本描述
- 版本对应不上,可能会出现兼容性问题,建议版本保持对应。
- 引入依赖
<spring.boot.version>2.4.5</spring.boot.version>
<spring.cloud.version>2020.0.2</spring.cloud.version>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开启feign客户端注解
/**
* 服务提供方启动类
*/
@EnableFeignClients
@SpringBootApplication
public class OauthServerApplication {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication.class,args);
}
}
/**
* feign客户端,能力提供者
*/
@RestController
@RequestMapping("/test")
public class TestFeignController {
@GetMapping("/get")
public String getTest(){
return "ok";
}
}
# 服务提供者应用配置
server:
port: 8082
spring:
application:
name: test-application
/**
* @Description:消费端启动类
* @ClassName:ResourceApplication
*/
@EnableFeignClients
@SpringBootApplication
public class ResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class,args);
}
}
@FeignClient(name = "oauthServiceFeign", path = "/test", url = "http://localhost:8082")
public interface OauthServiceFeign {
@GetMapping("/get")
String getTest();
}
# 服务消费端应用配置
server:
port: 8081
spring:
application:
name: resource-application
- 测试用例
/**
* 消费端测试类,调用服务端远程服务
*/
@SpringBootTest
@RunWith(SpringRunner.class)
class OauthServiceFeignTest {
@Autowired
OauthServiceFeign oauthServiceFeign;
@Test
void getTest() {
String test = oauthServiceFeign.getTest();
System.out.println("调用远程feign接口,返回结果: " + test);
}
}
- 返回结果
2021-05-31 17:25:23.877 INFO 9252 --- [ main] c.t.r.feign.OauthServiceFeignTest : Started OauthServiceFeignTest in 2.016 seconds (JVM running for 3.004)
调用远程feign接口,返回结果: ok
2021-05-31 17:25:24.274 INFO 9252 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Disconnected from the target VM, address: '127.0.0.1:63339', transport: 'socket'
Process finished with exit code 0
二、Open-Feign使用步骤解析
1. 引入spring-cloud-starter-openfeign
该pom引入:spring-cloud-openfeign-core,springboot启动过程中会加载spring.factories文件,并初始化该配置文件的配置类,从而达到自动装配。
# spring.factories 内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
1.简单分析spring.factories做了哪些事情
- FeignHalAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@ConditionalOnClass(RepresentationModel.class) // 指定RepresentationModel类存在类路径时,初始化该配置类 (该示例不存在该类,故该配置类在启动时未初始化)
@AutoConfigureAfter({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class })
@AutoConfigureBefore(HypermediaAutoConfiguration.class)
public class FeignHalAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter(
ObjectProvider<ObjectMapper> objectMapper, ObjectProvider<HalConfiguration> halConfiguration,
ObjectProvider<MessageResolver> messageResolver, ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<LinkRelationProvider> linkRelationProvider) {
ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
HalConfiguration configuration = halConfiguration.getIfAvailable(HalConfiguration::new);
CurieProvider curieProviderInstance = curieProvider
.getIfAvailable(() -> new DefaultCurieProvider(Collections.emptyMap()));
Jackson2HalModule.HalHandlerInstantiator halHandlerInstantiator = new Jackson2HalModule.HalHandlerInstantiator(
linkRelationProvider.getIfAvailable(), curieProviderInstance, messageResolver.getIfAvailable(),
configuration);
mapper.setHandlerInstantiator(halHandlerInstantiator);
if (!Jackson2HalModule.isAlreadyRegisteredIn(mapper)) {
Jackson2HalModule halModule = new Jackson2HalModule();
mapper.registerModule(halModule);
}
TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
RepresentationModel.class);
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
converter.setObjectMapper(mapper);
return converter;
}
}
- FeignAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class,
FeignEncoderProperties.class })
@Import(DefaultGzipDecoderConfiguration.class) // 引入了该配置类,并初始化该配置类:主要配置默认 Gzip 解码器,具体作用这里不做说明
public class FeignAutoConfiguration {
private static final Log LOG = LogFactory.getLog(FeignAutoConfiguration.class);
@Autowired(required = false) // 从容器中注入FeignClientSpecification实例 创建时机下面说明
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean // 作用不做说明
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean // 初始化feign上下文
public FeignContext feignContext() {
FeignContext context = new FeignContext();
//
context.setConfigurations(this.configurations);
return context;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Module.class, Page.class, Sort.class }) // 该示例不存在该类,故该配置类在启动时未初始化
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled", havingValue = "true")
protected static class FeignJacksonConfiguration {
@Bean
@ConditionalOnMissingBean(PageJacksonModule.class)
public PageJacksonModule pageJacksonModule() {
return new PageJacksonModule();
}
@Bean
@ConditionalOnMissingBean(SortJacksonModule.class)
public SortJacksonModule sortModule() {
return new SortJacksonModule();
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(FeignCircuitBreakerDisabledConditions.class)
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean // feign代理类
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "true")
protected static class CircuitBreakerPresentFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public Targeter defaultFeignTargeter() {
return new DefaultTargeter();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
}
}
// the following configuration is for alternate feign clients if
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
// for load-balanced clients.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class) // 该示例不存在该类,故该配置类在启动时未初始化
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class) // 该示例不存在该类,故该配置类在启动时未初始化
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
.connectionPool(connectionPool).build();
return this.okHttpClient;
}
@PreDestroy
public void destroy() {
if (this.okHttpClient != null) {
this.okHttpClient.dispatcher().executorService().shutdown();
this.okHttpClient.connectionPool().evictAll();
}
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class) // 该示例不存在该类,故该配置类在启动时未初始化
@ConditionalOnMissingBean(org.apache.hc.client5.http.impl.classic.CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@Import(org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.class)
protected static class HttpClient5FeignConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(org.apache.hc.client5.http.impl.classic.CloseableHttpClient httpClient5) {
return new ApacheHttp5Client(httpClient5);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OAuth2ClientContext.class) // 该示例不存在该类,故该配置类在启动时未初始化
@ConditionalOnProperty("feign.oauth2.enabled")
protected static class Oauth2FeignConfiguration {
@Bean
@ConditionalOnMissingBean(OAuth2FeignRequestInterceptor.class)
@ConditionalOnBean({ OAuth2ClientContext.class, OAuth2ProtectedResourceDetails.class })
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resource);
}
}
}
- FeignAcceptGzipEncodingAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(Client.class)
@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false) // 该示例不存在该配置项,故该配置类在启动时未初始化
// The OK HTTP client uses "transparent" compression.
// If the accept-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignAcceptGzipEncodingAutoConfiguration {
@Bean
public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor(
FeignClientEncodingProperties properties) {
return new FeignAcceptGzipEncodingInterceptor(properties);
}
}
- FeignContentGzipEncodingAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
// The OK HTTP client uses "transparent" compression.
// If the content-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
@ConditionalOnProperty("feign.compression.request.enabled") // 该示例不存在该配置项,故该配置类在启动时未初始化
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignContentGzipEncodingAutoConfiguration {
@Bean
public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(
FeignClientEncodingProperties properties) {
return new FeignContentGzipEncodingInterceptor(properties);
}
}
- FeignLoadBalancerAutoConfiguration
@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })// 该示例不存在该Bean,故该配置类在启动时未初始化
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
2. 开启Feign注解:@EnableFeignClients
1. @EnableFeignClients 作用
1. @EnableFeignClients 参数
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 重要:初始化FeignClientsRegistrar实例
public @interface EnableFeignClients {
// basePackages()属性的别名。 允许更简洁的注释声明
String[] value() default {};
// 用于扫描带注释组件的基本包
String[] basePackages() default {};
// 用于指定要扫描带注释组件的包。 将扫描指定的每个类的包
Class<?>[] basePackageClasses() default {};
// 客户端默认配置
Class<?>[] defaultConfiguration() default {};
// 用@FeignClient 注释的类列表。 如果不为空,则禁用类路径扫描
Class<?>[] clients() default {};
}
2. FeignClientsRegistrar作用
class FeignClientsRegistrar implements
ImportBeanDefinitionRegistrar, // 动态创建自定义Bean到Spring中: 即 @FeignClient 注解的bean的解析
ResourceLoaderAware,
EnvironmentAware {
......
// 解析入口, 即FeignClient Bean 注入容器时机
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 解析 @EnableFeignClients 注解
registerDefaultConfiguration(metadata, registry);
// 解析@FeignClient注解(重点)
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// @EnableFeignClients没有指定clients 时,会通过扫描class创建feignclient bean
if (clients == null || clients.length == 0) {
// 扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 类加载器
scanner.setResourceLoader(this.resourceLoader);
// 过滤类型:指定过滤出注解类型为FeignClient的 bean 的定义
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 拿到解析包路径:默认是@EnableFeignClients 注解所在类下的包
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 拿到所有FeignClient注解的bean的定义,进行进一步处理
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 校验@FeignClient只能注解在接口上
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 拿到 @FeignClient注解的所有属性值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注入@FeignClient 中指定的 configuration,注入的beanname 为 name + FeignClientSpecification
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注入 @FeignClient bean定义(重点)
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 构造FeignClient bean工厂,所有的FeignClient bean在这里生成
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
// 指定FeignClient 生成的步骤(简单看一下这个方法)
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
// 指定FeignClient 生成的步骤
public static <T> BeanDefinitionBuilder genericBeanDefinition(Class<T> beanClass, Supplier<T> instanceSupplier) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
// 注意这里指定生成bean的方式,在获取时通过该方法拿到代理对象
builder.beanDefinition.setInstanceSupplier(instanceSupplier);
return builder;
}