1、简介
feign是一个声明式的HTTP客户端,spring-cloud-openfeign将feign集成到spring boot中,在接口上通过注解声明Rest协议,将http调用转换为接口方法的调用,使得客户端调用http服务更加简单。
2、原理分析
看到客户端测试类中,我们只用了一行代码,就能完成对远程Rest服务的调用,相当的简单。为什么这么神奇,这几段代码是如何做到的呢?
2.1、@EnableFeignClients 注解声明客户端接口
入口是启动类上的注解@EnableFeignClients
,源代码:
1 | (RetentionPolicy.RUNTIME) |
@EnableFeignClients
的参数声明客户端接口的位置和默认的配置类。
2.2、@FeignClient注解,将接口声明为Feign客户端
1 | (ElementType.TYPE) |
2.3、FeignClientsRegistrar 注册客户端
@EnableFeignClients
注解上被注解了@Import(FeignClientsRegistrar.class)
,@Import
注解的作用是将指定的类作为Bean注入到Spring Context中,我们再来看被引入的FeignClientsRegistrar
1 | class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { |
FeignClientsRegistrar
类实现了3个接口:
- 接口
ResourceLoaderAware
用于注入ResourceLoader
- 接口
EnvironmentAware
用于注入Environment
- 接口
ImportBeanDefinitionRegistrar
用于动态向Spring Context中注册bean
ImportBeanDefinitionRegistrar
接口方法registerBeanDefinitions
有两个参数
- AnnotationMetadata 包含被@Import注解类的信息
- BeanDefinitionRegistry bean定义注册中心
2.4、registerDefaultConfiguration方法,注册@EnableFeignClients注解的全局默认configuration
1 | private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { |
取出@EnableFeignClients
注解的参数defaultConfiguration
,动态注册到spring Context中。
2.5、registerClientConfiguration 注册configuration
该方法用于注册feign配置,配置来源有2种:
@EnableFeignClients
注解的defaultConfiguration
属性注册时(全局默认配置),name为default.xxx
@FeignClient
注解的configuration
属性注册时(服务私有配置),name为contextId、name、value属性的值
有2个地方会调用该方法:
FeignClientsRegistrar.registerDefaultConfiguration()
FeignClientsRegistrar.registerFeignClients()
registerClientConfiguration 方法源码如下:
1 | private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { |
这里使用spring 动态注册bean的方式,注册了一个FeignClientSpecification
的bean。
2.6、FeignClientSpecification 客户端定义
一个简单的pojo,继承了NamedContextFactory.Specification
,两个属性String name
和 Class[] configuration
,用于FeignContext
命名空间独立配置,后面会用到。
1 | class FeignClientSpecification implements NamedContextFactory.Specification { |
2.7、registerFeignClients方法,注册feign客户端
1 | public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { |
这个方法主要逻辑是扫描注解声明的客户端(标有@FeignClient
的接口),调用registerFeignClient
方法注册到registry中。这里是一个典型的spring动态注册bean的例子,可以参考这段代码在spring中轻松的实现类路径下class扫描,动态注册bean到spring中。想了解spring类的扫描机制,可以断点到ClassPathScanningCandidateComponentProvider.findCandidateComponents
方法中,一步步调试。
2.8、registerFeignClient方法,注册单个客户feign端bean
1 | private void registerFeignClient(BeanDefinitionRegistry registry, |
registerFeignClient
方法主要是将FeignClientFactoryBean
工厂Bean
注册到registry中,spring初始化后,会调用FeignClientFactoryBean
的getObject方法创建bean注册到spring context中。
2.9、FeignClientFactoryBean 创建feign客户端的工厂
1 | class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { |
FeignClientFactoryBean
实现了FactoryBean
接口,是一个工厂bean

方法返回了Feign.Builder
对象:
1 | class FeignClientFactoryBean |
feign()方法设置了Feign.Builder
所必须的参数Encoder
/Decoder
/Contract
,其他参数都是可选的。这三个必须的参数从哪里来的呢?答案是在FeignContext
的构造器中,传入了默认的配置FeignClientsConfiguration
,这个配置类里面初始化了这三个参数。
1 | false) (proxyBeanMethods = |
可以看到,feign需要的decoder/enoder通过适配器共用springMVC中的HttpMessageConverters
引入。
feign有自己的注解体系,这里通过SpringMvcContract
适配了springMVC的注解体系。
2.9.4、SpringMvcContract 适配feign注解体系
SpringMvcContract继承了feign的类Contract.BaseContract
,作用是解析接口方法上的注解和方法参数,生成MethodMetadata
用于接口方法调用过程中组装http请求。
1 | public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { |
几个覆盖方法分别是处理类上的注解,处理方法,处理方法上的注解,处理方法参数注解,最终生成完整的MethodMetadata
。feign自己提供的Contract和扩展javax.ws.rx的Contract原理都是类似的。
2.9.5、FeignAutoConfiguration
Feign.Builder
生成后,就要用Target生成feign客户端的动态代理,这里FeignClientFactoryBean
中使用Targeter
,Targeter有两个实现类,分别是HystrixTargeter
和DefaultTargeter
,那么默认的Targeter又是怎么来的呢?
在spring-cloud-openfeign-core项目的META-INF\spring.factories
文件中有FeignAutoConfiguration
的自动化配置,关于spring.factories自动化配置的原理见springboot2.2自动注入文件spring.factories如何加载详解
spring.factories
Code1
2
3
4
5
6
7org.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.FeignLoadBalancerAutoConfigurationFeignAutoConfiguration
java1
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
115false) (proxyBeanMethods =
.class) (Feign
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
/**
* FeignClientSpecification包含了feign配置类,配置来源有2种:
* 1. @EnableFeignClients注解的defaultConfiguration属性注册时(全局默认配置),name为default.xxx
* 2. @FeignClient注解的configuration属性注册时(服务私有配置),name为contextId、name、value属性的值
*
* 有2个地方会调用该方法产生FeignClientSpecification:
* 1. FeignClientsRegistrar.registerDefaultConfiguration()
* 2. FeignClientsRegistrar.registerFeignClients()
*/
false) (required =
private Listconfigurations = new ArrayList<>();
/**
* spring cloud commons项目中的HasFeatures,表示使用了Feign特性,用于spring boot actuator监控
*/
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
/**
* 关键代码:feign客户端配置隔离的实现
* 创建FeignClientSpecification, 每个feign客户端能够有各自私有的configuration就靠它了,
* 他是命名空间隔离的applicationContext, 每个feign客户端都有自己的applicationContext
*/
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// 一定要注意这里把所有的配置都设置进去了
// 包括@EnableFeignClients的全局defaultConfiguration和@FeignClient的私有configuration配置
// FeignClientFactoryBean类中getTarget创建Feign.Bulider的get(context, beanType)都是从它里面取的
// feign客户端自身的applicationContext取不到就到父applicationContext中去取,
// 达到先取私有配置bean(例如encoder、client、logger、decoder、loggerLevel),
// 私有配置取不到再取全局配置bean的功能
context.setConfigurations(this.configurations);
return context;
}
/**
* 类路径中存在HystrixFeign这个类,使用hystrix实现的Targeter
*/
false) (proxyBeanMethods =
"feign.hystrix.HystrixFeign") (name =
protected static class HystrixFeignTargeterConfiguration {
/**
* 实现了熔断降级功能的Targeter
*/
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
/**
* 类路径中没有HystrixFeign这个类,使用默认实现DefaultTargeter
*/
false) (proxyBeanMethods =
"feign.hystrix.HystrixFeign") (
protected static class DefaultFeignTargeterConfiguration {
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
/**
* 引入了feign-httpclient.jar,使用apache HttpClient发送请求
*/
false) (proxyBeanMethods =
.class) (ApacheHttpClient
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
.class) (CloseableHttpClient
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
.class) (Client
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}
/**
* 引入了feign-okhttp.jar, 使用okhttp发送请求
*/
false) (proxyBeanMethods =
.class) (OkHttpClient
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
.class) (okhttp3.OkHttpClient
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
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;
}
}
}
- @FeignClient配置隔离体系总结
需要注意的是FeignAutoConfiguration
中的bean都是默认的全局配置, 而每个@FeignClient
都可以有其私有的configuration属性配置,在FeignClientFactoryBean.feign()
方法中创建Feign.builder().enocder(xx).decoder(xxx).targeter()
时调用的get(context, xx.class)
方法都是先从自身命名空间上下文中找bean(私有配置),如果找不到会从父上下文中找bean使用全局配置, 这就是为什么@FeignClient的configuration
属性可以没有的原因。
2.9.6、Targeter 生成接口动态代理
- DefaultTargeter
DefaultTargeter
很简单,直接调用HardCodedTarget
生成动态代理,HystrixTargeter
源码如下:
1 | class DefaultTargeter implements Targeter { |
HystrixTargeter
java1
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
60class HystrixTargeter implements Targeter {
publicT target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTargettarget) {
// 如果不是HystrixFeign.Builder,直接调用target生成代理
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
// 优先使用contextId属性, 如果contextId为空则使用name属性
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
// context是配置隔离的applicationContext
// 优先取client私有配置中的SetterFactory,若未配置私有的,则取全局默认的
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
// 私有的或全局的配置了SetterFactory
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
//找到fallback或者fallbackFactory,设置到hystrix中
Class fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder,
fallbackFactory);
}
// 无降级策略,直接调用target生成代理
return feign.target(target);
}
privateT targetWithFallbackFactory(String feignClientName,
FeignContext context,
Target.HardCodedTargettarget,
HystrixFeign.Builder builder,
Class fallbackFactoryClass) {
FallbackFactory fallbackFactory = (FallbackFactory) getFromContext(
"fallbackFactory", feignClientName, context, fallbackFactoryClass,
FallbackFactory.class);
return builder.target(target, fallbackFactory);
}
privateT targetWithFallback(String feignClientName,
FeignContext context,
Target.HardCodedTargettarget,
HystrixFeign.Builder builder,
Class fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder.target(target, fallbackInstance);
}
}HystrixFeign.Builder
java1
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
31public final class HystrixFeign {
public static final class Builder extends feign.Feign.Builder {
private Contract contract = new Default();
private SetterFactory setterFactory = new feign.hystrix.SetterFactory.Default();
publicT target(Target target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default(fallback) : null).newInstance(target);
}
publicT target(Target target, FallbackFactory fallbackFactory) {
return build(fallbackFactory).newInstance(target);
}
Feign build(final FallbackFactory nullableFallbackFactory) {
// 替换掉Feign.Bulider中默认的
// private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
// 由此可知当hystrix启用时feign客户端的动态代理实现是在HystrixInvocationHandler中
// 也就是说我们在contrller层调用feign客户端(例如UserFeignClient)时会进入到HystrixInvocationHandler中
// 拦截方法调用
super.invocationHandlerFactory(new InvocationHandlerFactory() {
public InvocationHandler create(Target target, Mapdispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
}
}HystrixInvocationHandler
java1
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
113final class HystrixInvocationHandler implements InvocationHandler {
private final Target target;
private final Mapdispatch;
private final FallbackFactory fallbackFactory; // Nullable
private final MapfallbackMethodMap;
private final MapsetterMethodMap;
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
...省略部分代码
HystrixCommand
3、loadBalance方法,客户端负载均衡
3.1、loadBalance方法的调用
如果@FeignClient
注解中没有配置url参数,将会通过loadBalance
方法生成Ribbon的动态代理:
1 | class FeignClientFactoryBean |
3.2、FeignRibbonClientAutoConfiguration
之前我们我们已经分析过在spring-cloud-openfeign-core项目的META-INF\spring.factories
文件中有FeignAutoConfiguration
的自动化配置,这里我们可以看到spring.factories
文件里面还有个FeignRibbonClientAutoConfiguration
自动化配置类,从名称就可以知道它与负载均衡相关。
spring.factories
Code1
2
3
4
5
6
7org.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.FeignLoadBalancerAutoConfigurationFeignRibbonClientAutoConfiguration
java1
2
3
4
5
6
7
8
9
10
11
12
13
14.class, Feign.class }) ({ ILoadBalancer
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
false) (proxyBeanMethods =
.class) (FeignAutoConfiguration
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// 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
.class, ({ HttpClientFeignLoadBalancedConfiguration
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
}可以看到它引入了HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration这3个类,我们接下来先看不引入apache http client、okhttp这些http库时的执行流程
LoadBalancerFeignClient的创建
java1
2
3
4
5
6
7
8
9
10
11
12false) (proxyBeanMethods =
class DefaultFeignLoadBalancedConfiguration {
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}LoadBalancerFeignClient
java1
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
45public class LoadBalancerFeignClient implements Client {
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
private final Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;
/**
* @param request 是feign自身的Request对象,持有发送http请求的Client实现
* @param options http请求的相关配置(connectTimeout、readTimeout)
*/
public Response execute(Request request, Request.Options options) throws IOException {
try {
//获取URI
URI asUri = URI.create(request.url());
//获取客户端的名称
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//创建RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
//配置
IClientConfig requestConfig = getClientConfig(options, clientName);
//获取FeignLoadBalancer,发请求,转换Response
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
}CachingSpringLoadBalancerFactory
java1
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
27public class CachingSpringLoadBalancerFactory {
protected final SpringClientFactory factory;
protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;
private volatile Mapcache = new ConcurrentReferenceHashMap<>();
public FeignLoadBalancer create(String clientName) {
// 先从缓存获取,缓存中没有再创建
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
// 取出负载均衡实现
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
// 创建FeignLoadBalancer或带有重试功能的RetryableFeignLoadBalancer
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
// 放到内存缓存中
this.cache.put(clientName, client);
return client;
}
}FeignLoadBalancer
java1
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
39public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
private final RibbonProperties ribbon;
protected int connectTimeout;
protected int readTimeout;
protected IClientConfig clientConfig;
protected ServerIntrospector serverIntrospector;
/**
* 注意此时的RibbonRequest参数的url属性已经从抽象的服务名经过ribbon负载均衡算法变成具体的域名或ip地址了
* 负载均衡的逻辑在其父类AbstractLoadBalancerAwareClient.executeWithLoadBalancer()方法中
*/
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
// http请求相关配置(connectTimeout、readTimeout)
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// 关键代码: request.client()返回的是feign的Client接口的实现(OkHttpClient、ApacheHttpClient)
// 它是在FeignClientFactoryBean.loadBalance()方法中将Client的实现设置到Feign.Builder中的
// 这里调用http请求框架真正发送请求获取响应
Response response = request.client().execute(request.toRequest(), options);
// 将feign的Reponse对象包装为ribbon的response
return new RibbonResponse(request.getUri(), response);
}
}AbstractLoadBalancerAwareClient
java1
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
51public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommandcommand = buildLoadBalancerCommand(request, requestConfig);
try {
// LoadBalancerCommand.submit()方法里面使用了rx-java,看起来很复杂。。。
return command.submit(
new ServerOperation() {
public Observablecall(Server server) {
// 这里负载均衡已经完成, server就是通过负载均衡算法选择的服务实例
// reconstructURIWithServer就是把抽象地址转换为server的具体域名或ip
// 得到最终http请求url
URI finalUri = reconstructURIWithServer(server, request.getUri());
// 将executeWithLoadBalancer方法参数的request中的uri替换成最终具体域名或ip的uri
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 调用了IClient接口中声明的 T execute(S request, IClientConfig requestConfig) 方法
// 该方法由该类的子类FeignLoadBalancer实现了,来真正发出http请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
protected LoadBalancerCommandbuildLoadBalancerCommand(final S request, final IClientConfig config) {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
LoadBalancerCommand.Builderbuilder = LoadBalancerCommand. builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri());
customizeLoadBalancerCommandBuilder(request, config, builder);
return builder.build();
}
}
3.3、FeignLoadBalancerAutoConfiguration
具有负载均衡功能的feign自动配置,
注意
@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
这个自动化配置条件,表明这个类的自动化配置在FeignRibbonClientAutoConfiguration
之后才执行,回顾上面的FeignRibbonClientAutoConfiguration
的执行流程可以知道,当启用ribbon时,FeignRibbonClientAutoConfiguration
引入的3个类HttpClientFeignLoadBalancedConfiguration
、OkHttpFeignLoadBalancedConfiguration
、DefaultFeignLoadBalancedConfiguration
本身会创建Feign的Client
接口的实现LoadBalancerFeignClient
,所以由FeignLoadBalancerAutoConfiguration
自动化配置的FeignBlockingLoadBalancerClient
不会被创建,因为Client
接口的实现已经由
FeignRibbonClientAutoConfiguration的自动化配置创建好了
LoadBalancerFeignClient`。
下面的源码分析建立在spring.cloud.loadbalancer.ribbon.enabled=false的情况下进行分析的
- FeignLoadBalancerAutoConfiguration源码如下:
1 | .class) (Feign |
可以看到同时引入了带有负载均衡功能的apache httpclient、okhttp及默认实现的feign client实现
- DefaultFeignLoadBalancerConfiguration
Client.Default底层使用jdk自带的HttpURLConnection发送请求
1 | false) (proxyBeanMethods = |
- OkHttpFeignLoadBalancerConfigurationjava
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16false) (proxyBeanMethods =
.class) (OkHttpClient
@ConditionalOnProperty("feign.okhttp.enabled")
.class) (BlockingLoadBalancerClient
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancerConfiguration {
public Client feignClient(okhttp3.OkHttpClient okHttpClient,
BlockingLoadBalancerClient loadBalancerClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
}
}
可以看到无论是DefaultFeignLoadBalancerConfiguration
还是OkHttpFeignLoadBalancerConfiguration
都是使用FeignBlockingLoadBalancerClient
,传入了具体http请求的实现,可以知道具体的负载均衡功能是由FeignBlockingLoadBalancerClient
实现的
- FeignBlockingLoadBalancerClientjava
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
35class FeignBlockingLoadBalancerClient implements Client {
private final Client delegate;
private final BlockingLoadBalancerClient loadBalancerClient;
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null,
"Request URI does not contain a valid hostname: " + originalUri);
// 使用负载均衡实现选择服务实例
ServiceInstance instance = loadBalancerClient.choose(serviceId);
if (instance == null) {
// 无可用实例
String message = "Load balancer does not contain an instance for the service "
+ serviceId;
if (LOG.isWarnEnabled()) {
LOG.warn(message);
}
return Response.builder().request(request)
.status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
// 将抽象的服务名替换成具体的域名、ip地址
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
.toString();
// 重新构造请求
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.requestBody());
// 委托给Client的具体实现((OkHttpClient、ApacheHttpClient))真正发出http请求
return delegate.execute(newRequest, options);
}
}
相比而言spring-cloud-loadbalancer与feign适配的代码比ribbon就简洁了很多很多。。。
4、总结
feign本身是一款优秀的开源组件,spring cloud feign又非常巧妙的将feign集成到spring boot中。
本文通过对spring cloud feign源代码的解读,详细的分析了feign集成到spring boot中的原理,使我们更加全面的了解到feign的使用。
spring cloud feign也是一个很好的学习spring boot的例子,从中我们可以学习到:
- spring boot注解声明注入bean
- spring类扫描机制
- spring接口动态注册bean
- spring命名空间隔离ApplicationContext
文章大部分来源于:http://techblog.ppdai.com/2018/05/28/20180528/
本人在其基础上加入自己的理解