springcloud源码-feign源码解析

发布 : 2019-05-24 分类 : springcloud 浏览 :

FeignClient的使用

  • 定义接口

    1
    2
    3
    4
    5
    6
    7
    @FeignClient(value = "eureka-client2",configuration = {FeignClientConfig.class})
    public interface DemoClient {

    @RequestMapping(value = "/demo/hello",method = RequestMethod.GET)
    public String callDemo();

    }
  • Springboot启动类中增加启动注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    @EnableCircuitBreaker
    public class EurekaClientApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaClientApplication.class,args);
    }

    @Bean
    Request.Options options() {
    return new Request.Options(1000*10,40*1000);
    }
    }
  • 在使用的时候,直接自动化注入@Autowired。

方法说明:英文阅读,不翻译了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";

/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";

/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";

/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";

/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";

/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;

/**
* A custom <code>@Configuration</code> for the feign client. 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
*/
Class<?>[] configuration() default {};

/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;

/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;

/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";

/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;

原理解析

先说下大概到思路:
1、定义使用feignclient的注解接口
2、启动类中使用Import类,导入FeignClient的实现机制。
3、这个导入类会去初始化Feign的配置文件,进行一些环境变量的初始化和准备工作。
4、创建一个BeanFactory,在程序中@AutoWired的时候,就getObject()。这个getObject就是创建一个FeignClient的代理类。
5、因为接口方式,所以采用的是jdk动态代理实现。
6、实际调用,由代理类进行直接调用。

EnableFeignClients注解

  • 这个注解类里的方法就暂时忽略不看了,大家自己看看,基本是扫描的package定义,还有就是指定哪些客户端生效的配置等。
  • Import注解导入了FeignClientsRegistrar类。这个在springboot用的特别多。

FeignClientsRegistrar类

  • 实现了ImportBeanDefinitionRegistrar这个接口,他的作用基本就是注册一个bean进容器。
  • 其他接口基本是一些loader的中间件接口,还有环境变量中间件。

这个类主要做了两件大事:
1、初始化配置。
2、注册feignclient客户端。

1
2
3
4
5
6
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}

registerDefaultConfiguration方法:

  • 对于registerDefaultConfiguration,主要是加载默认配置。由于EnableFeignClients中有一些方法来对feign进行全局配置,所以,这里就是注册全局的配置。
  • 通过AnnotationMetadata拿到注解的属性和属性值。
  • 对于defaultConfiguration这个自定义的配置类,进行单独的处理。
  • 对于这些自定义的配置,会封装在FeignClientSpecification类中,然后注册进spring容器中。

registerFeignClients方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//定义个scanner。判断这个候选的component是否符合条件。是否是Annotation。传入的是一个对象的定义。
ClassPathScanningCandidateComponentProvider scanner = getScanner();
//设置资源对象
scanner.setResourceLoader(this.resourceLoader);

Set<String> basePackages;

//获取注解类的注解属性列表。
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//定义一个注解的filter。主要是过滤出来FeignClient注解。
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//看是否定义特殊的加载对象,如果没定义,就全局扫描,如果定义了,就只加载定义的feignclient对象。
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的注解类。
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");
//获取每个feignclient的属性,即注解中的方法参数,name ,URL 等。
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
//获取name,value或者serviceId。
String name = getClientName(attributes);
//配置注册。
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//对象注册。
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

看下核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//定一个FeignClientFactoryBean的builder。
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
//这个name基本是http://service-id/....
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"));
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;
}

BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注册了一个FeignClientFactoryBean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

接下来,需要看看FeignClientFactoryBean这个类:
这类实现了FactoryBean接口,getObject接口返回实例化的对象。
我们来看下这个类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/**
* @author Spencer Gibb
* @author Venil Noronha
* @author Eko Kurniawan Khannedy
* @author Gregor Zurowski
*/
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
/***********************************
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition.
***********************************/

private Class<?> type;

private String name;

private String url;

private String path;

private boolean decode404;

private ApplicationContext applicationContext;

private Class<?> fallback = void.class;

private Class<?> fallbackFactory = void.class;

@Override
public void afterPropertiesSet() throws Exception {
Assert.hasText(this.name, "Name must be set");
}

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
}

protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);

// @formatter:off
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;
}

protected void configureFeign(FeignContext context, Feign.Builder builder) {
//FeignClientProperties这个类中的配置,可以在yml文件中进行设置:feign.client前缀开头的。
FeignClientProperties properties = 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.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}

//对于这个方法,我们发现,如下类是可以在容器启动的时候,直接重写的:
//Logger.Level
//Retryer
//ErrorDecoder
//Request.Options
//RequestInterceptor
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
this.name, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}

if (decode404) {
builder.decode404();
}
}

protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
if (config == null) {
return;
}

if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}

if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}

if (config.getRetryer() != null) {
Retryer retryer = getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}

if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}

if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
// this will add request interceptor to builder, not replace existing
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}

if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
}

private <T> T getOrInstantiate(Class<T> tClass) {
try {
return applicationContext.getBean(tClass);
} catch (NoSuchBeanDefinitionException e) {
return BeanUtils.instantiateClass(tClass);
}
}

protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}

protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.name, type);
}

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}

throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

//这个是核心方法。
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);

//没有url,用的是serviceId之类的。
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
//有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 lod 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);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}

private String cleanPath() {
String path = this.path.trim();
if (StringUtils.hasLength(path)) {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}

...代码略....

}

看下loadBalance方法吧:

  • 这里基本就是返回了一个Proxy代理类。代理类这个接口所有的方法的执行。
  • Targeter就是实现类HystrixTargeter。
  • 对于Client,有一个实现类是LoadBalanceFeignClient。
1
2
3
4
5
6
7
8
9
10
11
12
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}

throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
  • 我们看下handler:SynchronousMethodHandler
    这个里面的invoke方法,就是代理的真正执行方法。在执行feign方法的时候,会调用invoke执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
    try {
    return executeAndDecode(template);
    } catch (RetryableException e) {
    retryer.continueOrPropagate(e);
    if (logLevel != Logger.Level.NONE) {
    logger.logRetry(metadata.configKey(), logLevel);
    }
    continue;
    }
    }
    }
本文作者 : braveheart
原文链接 : https://zhangjun075.github.io/passages/springcloud-feign/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

知识 & 情怀 | 二者兼得

微信扫一扫, 向我打赏

微信扫一扫, 向我打赏

支付宝扫一扫, 向我打赏

支付宝扫一扫, 向我打赏

留下足迹