Spring Cloud服务调用

  1. OpenFeign核心api
  2. Spring Cloud Feign使用
  3. RestTemplate自定义实现服务调用
  4. Spring Cloud Feign源码分析

Spring Cloud服务调用

版本信息

Spring Cloud : Hoxton.SR1

Spring Boot : 2.2.2.RELEASE

Zookeeper : 3.5.6 (注册中心使用)

OpenFeign核心api

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket

  • feign.Feign 核心类
  • feign.Contract 服务接口类的注解和值解析
  • feign.Client 调用
  • feign.Retryer 重试
  • feign.codec.Encoder 序列化,对象转成Http请求
  • feign.codec.Decoder 反序列化,Http请求转换为对象
  • feign.QueryMapEncoder 查询参数的编码
  • feign.codec.ErrorDecoder 错误信息编码
  • feign.Request 请求参数
  • feign.InvocationHandlerFactory 控制反射方法分派

Spring Cloud Feign使用

服务端

一个简单的对外提供服务,基于Zookeeper注册中心

  1. 添加pom依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--zookeeper 客户端-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
  2. 简单的对外提供服务端点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 演示服务端点
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/2/7
    */
    @RestController
    public class EchoController {

    @Autowired
    private Environment environment;


    @GetMapping("/echo")
    public String echo(@RequestParam String message) {
    // 由于采用的是随机端口,这地方必须采用这个方式获取端口
    String port = environment.getProperty("local.server.port");
    return "ECHO(" + port + "):" + message;
    }
    }
  3. 配置文件application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    application:
    name: feign-server
    cloud:
    zookeeper:
    connect-string: 127.0.0.1:2181
    server:
    port: 0
  4. 服务启动程序类FeignServerApplication

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * Feign 服务端应用
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/2/7
    */
    @SpringBootApplication
    public class FeignServerApplication {

    public static void main(String[] args) {
    SpringApplication.run(FeignServerApplication.class, args);
    }
    }

    启动服务,根据启动日志查看本地的随机端口,此次端口是60981

    http://127.0.0.1:60981/echo?message=Hello 返回信息ECHO(60981):Hello

客户端

  1. 添加pom依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--zookeeper 客户端-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    <!--openfeign 客户端调用-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  2. 服务提供接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 服务提供echo服务
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/2/7
    */
    @FeignClient("feign-server") //标注是Feign Client
    public interface EchoService {

    @GetMapping("/echo")
    String echo(@RequestParam("message") String message);
    }
  3. 客户端提供服务端点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 演示服务端点
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/2/7
    */
    @RestController
    public class EchoController {

    @Autowired
    private EchoService echoService;

    @GetMapping("/echo")
    public String echo(String message) {
    return this.echoService.echo(message);
    }
    }
  4. 配置文件application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    application:
    name: feign-client
    cloud:
    zookeeper:
    connect-string: 127.0.0.1:2181
    server:
    port: 8090
  5. 服务启动程序类FeignClientApplication

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * Feign 客户端服务
    *
    * @author FelixFly <chenglinxu@yeah.net>
    * @date 2020/2/7
    */
    @SpringBootApplication
    @EnableFeignClients(clients = EchoService.class) //启用Feign Client
    public class FeignClientApplication {

    public static void main(String[] args) {
    SpringApplication.run(FeignClientApplication.class, args);
    }
    }

    启动服务,http://localhost:8090/echo?message=hello 返回信息ECHO(60981):hello

RestTemplate自定义实现服务调用

自定义客户端调用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* rest 客户端调用
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestFeignClient {

/**
* 服务名称
*
*/
String value() default "";
}

自定义客户端调用服务

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 服务提供echo服务
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
@RestFeignClient("feign-server") //标注是Feign Client
public interface RestEchoService {

@GetMapping("/echo")
String echo(@RequestParam("message") String message);
}

自定义客户端启用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 启用Rest 客户端
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RestFeignClientsRegistrar.class)
public @interface EnableRestFeignClients {

/**
* {@link RestFeignClient}接口列表
*/
Class<?>[] clients() default {};
}

导入RestFeignClientsRegistrar

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
/**
* rest 客户端注册
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
public class RestFeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

private BeanFactory beanFactory;

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 类加载类
ClassLoader classLoader = metadata.getClass().getClassLoader();
// 获取EnableRestFeignClients注解的内容
Map<String, Object> annotationAttributes = metadata
.getAnnotationAttributes(EnableRestFeignClients.class.getName());
// 获取clients
Class<?>[] clients = (Class<?>[]) annotationAttributes.get("clients");
Stream.of(clients)
.filter(Class::isInterface)
.filter(interfaceClass ->
Objects.nonNull(AnnotationUtils.findAnnotation(interfaceClass, RestFeignClient.class)))
.forEach(restClientClass -> {
// 获取服务名称
RestFeignClient restFeignClient = AnnotationUtils
.findAnnotation(restClientClass, RestFeignClient.class);
String serviceName = restFeignClient.value();


Object proxyInstance = Proxy.newProxyInstance(classLoader, new Class<?>[]{restClientClass},
new RestFeignClientInvocationHandler(serviceName, this.beanFactory));

// 注册Bean
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(RestFeignClientFactoryBean.class);
beanDefinitionBuilder.addConstructorArgValue(proxyInstance);
beanDefinitionBuilder.addConstructorArgValue(restClientClass);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
String beanName = serviceName + restClientClass.getName();
beanDefinition.setFactoryBeanName("&" + beanName);
registry.registerBeanDefinition(beanName, beanDefinition);

});
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}


private static class RestFeignClientFactoryBean implements FactoryBean<Object> {

private final Object object;
private final Class<?> objectType;

private RestFeignClientFactoryBean(Object object, Class<?> objectType) {
this.object = object;
this.objectType = objectType;
}


@Override
public Object getObject() throws Exception {
return this.object;
}

@Override
public Class<?> getObjectType() {
return this.objectType;
}
}
}

动态代理调用RestFeignClientInvocationHandler

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
/**
* Rest 客户端调用
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
public class RestFeignClientInvocationHandler implements InvocationHandler {

private final String serverName;

private final BeanFactory beanFactory;

public RestFeignClientInvocationHandler(String serverName, BeanFactory beanFactory) {
this.serverName = serverName;
this.beanFactory = beanFactory;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 带有requestMapping的方法
RequestMapping requestMapping =
AnnotatedElementUtils
.findMergedAnnotation(method, RequestMapping.class);
// 没有RequestMapping的方法调用原来的方法
if (Objects.isNull(requestMapping)) {
return method.invoke(proxy, args);
}
// 路径,默认只取第一个
String path = requestMapping.path()[0];
// beanFactory的默认实现是DefaultListableBeanFactory,可以直接强制转成ListableBeanFactory
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
// 获取所有的RestTemplate的Bean
Map<String, RestTemplate> restTemplateMap = listableBeanFactory
.getBeansOfType(RestTemplate.class);
// 获取标注有的@LoadBalanced的RestTemplate的BeanName
String restTemplateName = restTemplateMap.keySet()
.stream()
.filter(beanName -> Objects
.nonNull(listableBeanFactory.findAnnotationOnBean(beanName,
LoadBalanced.class)))
.findAny()
.orElseThrow(() -> new RuntimeException("没有@LoadBalanced标注的RestTemplate"));
// 获取标注有的@LoadBalanced的RestTemplate
RestTemplate restTemplate = restTemplateMap.get(restTemplateName);
StringBuilder paramBuilder = new StringBuilder("http://").append(serverName).append(path).append("?");
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[0];
RequestParam requestParam = AnnotationUtils.getAnnotation(parameter, RequestParam.class);
if (Objects.isNull(requestParam)) {
continue;
}
String paramName = requestParam.value();
String paramValue = String.valueOf(args[i]);
paramBuilder.append("&").append(paramName).append("=").append(paramValue);
}
String url = paramBuilder.toString();
RequestMethod[] requestMethods = requestMapping.method();
// 默认取第一个
HttpMethod httpMethod = HttpMethod.resolve(requestMethods[0].name());
ResponseEntity<?> exchange = restTemplate.exchange(url, httpMethod, null,
method.getReturnType());
return exchange.getBody();
}
}

启动添加启用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Feign 客户端服务
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
@SpringBootApplication
@EnableFeignClients(clients = EchoService.class) //启用Feign Client
@EnableRestFeignClients(clients = RestEchoService.class) // 启动Rest Client
public class FeignClientApplication {

public static void main(String[] args) {
SpringApplication.run(FeignClientApplication.class, args);
}

@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

演示服务端点

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
/**
* 演示服务端点
*
* @author FelixFly <chenglinxu@yeah.net>
* @date 2020/2/7
*/
@RestController
public class EchoController {

@Autowired
private EchoService echoService;

@Autowired
private RestEchoService restEchoService;

@GetMapping("/echo")
public String echo(String message) {
return this.echoService.echo(message);
}

@GetMapping("/rest/echo")
public String restEcho(String message) {
return this.restEchoService.echo(message);
}
}

启动服务,http://localhost:8090/rest/echo?message=hello 返回信息ECHO(60981):hello

Spring Cloud Feign源码分析

@EnableFeignClients分析

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

...
}

导入了FeignClientsRegistrar

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
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar

private ResourceLoader resourceLoader;

private Environment environment;

FeignClientsRegistrar() {
}

...

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册默认配置
registerDefaultConfiguration(metadata, registry);
// 注册Feign 客户端
registerFeignClients(metadata, registry);
}

private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}

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)));
}

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"));
// 注册Feign 客户端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 定义了一个FeignClientFactoryBean的BeanDefinition
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 });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

...

}

注册了FeignClientFactoryBean

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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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 contextId;

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.contextId, "Context id must be set");
Assert.hasText(this.name, "Name must be set");
}

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 properties = this.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.contextId),
builder);
}
else {
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}

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.contextId, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
if (queryMapEncoder != null) {
builder.queryMapEncoder(queryMapEncoder);
}
if (this.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();
}
}

if (Objects.nonNull(config.getEncoder())) {
builder.encoder(getOrInstantiate(config.getEncoder()));
}

if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder()));
}

if (Objects.nonNull(config.getContract())) {
builder.contract(getOrInstantiate(config.getContract()));
}
}

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

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

protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, 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 {
return getTarget();
}

/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
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) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

...

}

构建了一个Feign调用,客户端有两种LoadBalancerFeignClient以及FeignBlockingLoadBalancerClient

配置属性FeignClientProperties

OpenFeign核心api在Spring Cloud中的扩展

org.springframework.cloud.openfeign.FeignClientsConfiguration默认的配置类

  • feign.Contract 服务接口类的注解和值解析

    • org.springframework.cloud.openfeign.support.SpringMvcContract

      支持Spring MVC的注解,比如@RequestMapping@PathVariable等等

      参数注解处理核心接口:org.springframework.cloud.openfeign.AnnotatedParameterProcessorr

      • @PathVariable PathVariableParameterProcessor
      • @RequestParam RequestParamParameterProcessor
      • @RequestHeader RequestHeaderParameterProcessor
      • @SpringQueryMap QueryMapParameterProcessor
  • feign.Client 调用

    • org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient Ribbon客户端调用
    • org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient 阻塞的Feign客户端,Spring Cloud LoadBalanced实现
  • feign.Retryer 重试

    • Retryer.NEVER_RETRY 不进行重试
  • feign.codec.Encoder 序列化,对象转成Http请求

    • new SpringEncoder(this.messageConverters) 普通的反序列化
    • new PageableSpringEncoder(new SpringEncoder(this.messageConverters)) 带有分页参数的反序列化

    从上可以得知,这个序列是通过HttpMessageConverter组合去处理的

  • feign.codec.Decoder 反序列化,Http请求转换为对象

    • new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)))

    从上可以得知,这个反序列是通过HttpMessageConverter组合去处理的


Spring Cloud服务调用
http://example.com/2020/02/07/SpringCloud/Spring Cloud服务调用/
作者
FelixFly
发布于
2020年2月7日
许可协议