SpringCloud.Honxton 版本 OpenFeign原理
前置说明
上篇介绍了openfeign的使用和原理, 那么这篇将介绍和springcloud 的整合部分. 有了上一篇的基础, 那么在分析原理的时候就简单很多了, 更多的将关注于springcloud 的整合部分.
使用的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
如何使用
按照惯例还是先介绍如何使用feign
@FeignClient("client2") // service-id
public interface HelloOpenfeignSpringcloud {
@RequestMapping(name=" /feign",method = RequestMethod.GET, produces="application/x-www-form-urlencoded")
String feign(@RequestParam("feign") String feign);
@RequestMapping(name="/feignPost",method = RequestMethod.POST, produces="application/x-www-form-urlencoded")
String feignPost(@RequestParam("feign") String feign);
@RequestMapping(name = "/feignJson",method = RequestMethod.POST , produces="application/json")
String feignJson(@RequestBody String feign);
}
另外还需要加上 @EnableFeignClients
注解放到启动类上
可以看到, 整合之后的用法的优点有以下几点
- 不在需要手动调用代理的生成
- 可以使用我们熟悉的springmvc注解
- 结合注册中心之后,不需要我们指定url, 且能支持负载均衡
…
处此之外, springcloud 提供了熔断器和feign的整合, 用法也很简单, 就是给接口增加个实现类,作为降级处理的逻辑.
当然缺点也有很多啦, 包括传递文件时会有些问题, 然后传递对象必须要用@RequestBody, 但是也算是一个公认较好的客户端远程调用框架。
springcloud 和 openfeign 整合原理
了解完基本使用, 那么就是揭开springcloud整合feign面纱的时候了
以下是所有的feign的自动配置类
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
其中最重要的就是FeignAutoConfiguration
以及FeignRibbonClientAutoConfiguration
或者FeignLoadBalancerAutoConfiguration
那么我们一个个分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class) // 条件装配
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
@Autowired(required = false) // 单独的feignclient配置
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean // feignclient的 spring命名工厂, 主要根据不同的serviceId创建不同的applicationContext 然后获取专属的配置, 很方便的和其他的client进行配置的隔离
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
// 是否开启了 feign和hystrix的整合
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
// 这里的Targeter接口是, springcloud 的一个抽象, 默认的实现是DefaultTargeter ,会委托Feign.Builder的target方法, 也就是我上篇讲到的
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
// 没有开启hystrix 的情况
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
// feign 的底层调用实现 , 这里只粘贴了和httpclient相关的bean 定义
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class) // 一般不引入这个是不会触发这个配置类
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
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;
}
// 这个client接口就是上篇讲到的用于替换底层实现的地方, 这里用httpclient来实现了, 但是通常不是使用这里的这个bean后面会有提到
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() throws Exception {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
}
}
}
...
}
下面来分别介绍一下feign底层负载均衡的实现, 这里有2种, 一种是ribbon的底层整合,另一种是我之前的文章介绍过的spring自己研发的loadbalancer.
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class) //在上一个自动配置之前配置
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class }) // 这里导入 相关底层client的是实现的client的bean配置
public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
// 用于创建 负载均衡器 给Client 进行execute的委派执行
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
}
//这里介绍一个导入的底层client实现, 默认其实还是使用feign原生的DefaultClient
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
另外看下另一个负载均衡器和feign的整合
// 实现非常的简单
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class }) //仅仅是进行了配置导入, 和上面ribbon 一样
class FeignLoadBalancerAutoConfiguration {
}
// 这个导入的配置类也是提供一个Client的实现
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
看完上面的全部配置类,我相信可能大家都晕了, 其实不用担心, 因为下面就会讲FeignClient扫描创建FeignClientFactoryBean的时候, 到时候就会将上面配置bean都注入进来使用.
首先是 @EnableFeignClients
注解就是开启扫描的功能
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) //导入扫描器
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};//配置扫描的包
...
}
//@FeignClient扫描器
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
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() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
// 循环扫描和注册beandefinition
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition 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
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 注册到beanfactotry
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
以上是关于扫描相关的实现原理, 所有打着注解的都接口, 最后都变成了 FeignClientFactoryBean
形式注册成bean了, 接下来就简单了, 只要我们盯着 FeignClientFactoryBean
的getObject方法就能揭晓最后的谜底了.
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 获取Builder, 之后就是和上篇讲的源码类似了
Feign.Builder builder = feign(context);
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) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
// 这里的 Targeter前面也有介绍, 这里默认是 DefaultTargeter
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, //这里进去就是就会惊奇的发现就是上一篇的 入口方法
new HardCodedTarget<>(this.type, this.name, url));
}
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off 其实我看到这里会有个疑问, 这个bean是怎么从 对应的命名applicationContext获取的, 那么下面会给出答案
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));
// @formatter:on
configureFeign(context, builder);
return builder;
}
}
之前遗漏的一个小细节, 关于 Feign.Builder.class 这个bean是如何获取的
// 这个是最早介绍的被注册成bean 的, 其实还包含了一个配置类 FeignClientsConfiguration
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
...
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
...
}
总结
最后总结一下, springcloud为了整合feign做了很多的工作, 包括很多为了结合负载均衡器 以及 hystrix 做的很多扩展实现。 这也是导致配置类急剧膨胀 ,让我们看的眼花缭乱, 不知道看完这篇文章有没有让你拨开云雾见青天的赶脚,但是我也是尽可能的去将我所理解的讲出来了, 要是觉得我有讲错的可以在下方评论交流。
转载:https://blog.csdn.net/cdy1996/article/details/104995119