Spring Cloud OpenFeign源码分析
Posted IT老刘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud OpenFeign源码分析相关的知识,希望对你有一定的参考价值。
文章目录
1.如何使用spring cloud feign
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开始FeignClient
@SpringBootApplication
@EnableFeignClients
public class AdminWebApp {
public static void main(String[] args) {
SpringApplication.run(AdminWebApp.class,args);
}
}
- FeignClient接口
@FeignClient(value = "ItemCatService")
public interface ItemCatClient {
}
从官网的例子中,可以看出是通过注解驱动的,所以从注解开始看起。
2.spring cloud feign是如何工作的
Feign涉及了两个注解,一个是@EnableFeignClients
,用来开启 Feign,另一个是@FeignClient
,用来标记要用 Feign 来拦截的请求接口。
- @EnableFeignClients注解:
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
- @FeignClient注解:
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
/** @deprecated */
@Deprecated
String serviceId() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
String url() default "";
boolean decode404() default false;
Class<?>[] configuration() default {};
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
@EnableFeignClients
是关于注解扫描的配置,比如扫描路径,配置等。@FeignClient
则是关于对该接口进行代理的时候,一些实现细节的配置,比如访问url是什么, fallback
方法,关于404的请求是抛错误还是正常返回。
3.注册client
先关注对EnableFeignClients
的处理,可以看出它使用了@Import(FeignClientsRegistrar.class)
,看名字可知是一个注册器,通过扫描某个特性的类,将bean
注册到IOC中。Spring 通过调用其 registerBeanDefinitions
方法来获取其提供的 bean definition
。
- FeignClientsRegistrar类
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册configuration
this.registerDefaultConfiguration(metadata, registry);
//注册注解
this.registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//获取注解@EnableFeignClients 下设置的属性值
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
//判断传入的defaultConfiguration的是不是topClass,所谓topClass就是说此类不是别的类的内部类
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
//加载FeignClientSpecification bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注册
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}
这里会往 Registry
里面添加一个BeanDefinition
,即 FeignClientSpecification
,configuration
是通过 EnableFeignClients
注解的 defaultConfiguration
参数传入。
- registerFeignClients方法
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());
// 扫描带有FeignClient注解的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//获取@EnableFeignClients 中clients的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//如果没有设置,那么加入要扫描的注解和扫描的包
scanner.addIncludeFilter(annotationTypeFilter);
// 确定扫描的包路径列表
basePackages = getBasePackages(metadata);
}
else {
//如果设置了,最终扫出来的Bean必须是注解中设置的那些
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)));
}
//循环扫描,并把根据注解信息,进行相关注册
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();
//必须注解在interface上
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);
//将属性设置到FeignClientFactoryBean 中
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
//设置Autowire 类型
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "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;
}
//注册bean
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
另一个往 Registry 里面添加的 BeanDefinition
则是FeignClientFactoryBean
,负责注册FeignClient
。
也就是说,Feign的注册一共分为一下几步:
- 扫描
@EnableFeignClients
注解,如果有defaultConfiguration
属性配置,则将configuration
注册到BeanDefinition
中,如果不指定的话,spring 提供的默认配置是FeignClientsConfiguration
。 - 扫描
basePackage
下面所有包含了@FeignClient
注解的类 - 如果
@EnableFeignClients
中配置了clients
属性,则扫描出来的bean
只有在clients
中配置的那些 - 循环扫描
@FeignClient
注解,如果配置了configuration
,则将configuration
按照 1 注册到BeanDefinition
中,也就是说Feign
既支持用作统一的默认的Config
作为全局配置,也可以分别在@FeignClien
t中单独配置configuration
作为局部配置。 - 将
@FeignClient
中的其他配置设置到FeignClientFactoryBean
中。 - 最后调用
FeignClientFactoryBean
#getObject
来创建client实例。
4. FeignClientFactoryBean
FeignClientFactoryBean会生成一个@FeignClient注解的对应的service实例。
位置:org.springframework.cloud.openfeign.FeignClientFactoryBean
public Object getObject() throws Exception {
return this.getTarget();
}
<T> T getTarget() {
FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
Builder builder = this.feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}
this.url = this.url + this.cleanPath();
return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
} else {
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + this.cleanPath();
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(以上是关于Spring Cloud OpenFeign源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Feign 系列(05)Spring Cloud OpenFeign 源码解析