3.1 RestTemplate简介
\
RestTemplate是Spring Resources中一个访问第三方RESTful API接口的网络请求框架。RestTemplate的设计原则和其他的Spring Template(例如JdbcTemplate)类似,都是为了执行复杂任务提供了一个具有默认行为的简单方法。
RestTemplate是用来消费REST服务的,所以RestTemplate的主要方法都与REST的HTTP协议的一些方法紧密相连,例如HEAD、GET、POST、PUT、DELETE、OPTIONS等方法,这些方法在RestTemplate类对应的方法为headForHeaders(),getForObject()、postForObject()、put()、delet()等。
举例说明,在订单服务通过RestTemplate的getForObject方法调用支付服务,并且将调用结果反序列化成Payment对象,代码如下。
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer id) {
String url = "http://localhost:9001/payment/" + id;
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("cloud-payment-service");
ServiceInstance serviceInstance = serviceInstances.get(0);
url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
RestTemplate支持常见的Http协议请求方法,例如post, get, delete等,所以用RestTemplate很容易构建RESTfule API。上述案例结果返回json对象,使用jackson框架完成。
3.2 LoadBalancer负载均衡
\
负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如Nginx。另一种是将负载均衡逻辑一代吗的形式封装到服务消费者的客户端上,服务消费者客户端维护了一份服务提供者的信息列表,有了信息表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。
SpringCloud原有的客户端负载均衡方案Ribbon已经被废弃,取而代之的是SpringCloud LoadBalancer,LoadBalancer是Spring Cloud Commons的一个子项目,他属于上述的第二种方式,是将负载均衡逻辑封装到客户端中,并且运行在客户端的进程里。
在Spring Cloud构件微服务系统中,LoadBalancer作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate相结合,另一种是和Feign相结合,Feign已经默认集成了LoadBalancer,关于Feign下一章讲解。
\
3.2.1 LoadBalancer整合RestTemplate
在支付微服务工程中进行如下更改。
- 配置文件
在application.yml配置文件中,使用spel指定端口,表示存在port参数使用port参数,不存在使用默认9001端口, 启动支付服务时,可以通过指定-Dport=9000,指定支付服务使用不同端口启动,具体参考后面内容。
server:
port: ${port:9001}
- PaymentController
在提供支付服务时,把端口打印出来,方便查看测试效果。代码如下。
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/{id}")
public ResponseEntity<Payment> payment(@PathVariable("id") Integer id) {
Payment payment = new Payment(id, "支付成功,服务端口=" + serverPort);
return ResponseEntity.ok(payment);
}
}
\
- OrderController
在OrderController中,使用serviceId调用支付服务,此时LoadBalancer负载均衡生效,从多个服务提供者节点轮询选择一个使用。
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer id) {
String url = "http://cloud-payment-service/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
- 启动并测试
启动两个支付微服务工程,端口分别是9000和9001,因为application.yml配置文件中使用${port:9001}配置端口,其中9000节点启动配置如图3-1所示。
图3-1 使用-Dport=9000启动支付服务
另一个支付节点,不指定-Dport,使用默认9001端口启动,这时准备了2个支付微服务节点。Eureka注册效果如图3-2所示。
图3-2 多个支付服务注册效果
接下来在浏览器多次访问http://localhost:9002/order/payment/123456时,负载均衡器起了作用,结果9000,9001轮流出现。
支付成功,服务端口=9000
支付成功,服务端口=9001
\
3.2.2 LoadBlancerClient简介
\
负载均衡的核心类为LoadBalancerClient,LoadBalancerClient可以获取负载均衡的服务提供者实例信息。在OrderController增加演示代码如下。
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/test-load-balancer")
public String testLoadBalancer() {
ServiceInstance instance = loadBalancerClient.choose("cloud-payment-service");
return instance.getHost() + ":" + instance.getPort();
}
重启工程,浏览器访问http://localhost:9002/order/test-load-balancer,发现浏览器轮流显示如下内容
localhost:9000
localhost:9001
3.3 LoadBalancer源码解析
3.3.1 类的调用顺序
\
当时使用有@LoadBalance注解的RestTemplate时,设计的类的调用关系如图3-3所示
图3-3 LoadBalancer类调用顺序
关键类解析
- LoadBalancerRequestFactory: 一个工厂, 包装一个为HttpRequest对象,回调对象LoadBalancerRequest。
- LoadBalancerClient: 用于根据 serviceId 选取一个 ServiceInstance, 执行从 LoadBalancerRequestFactory 获得的那个回调。
- LoadBalancerInterceptor:RestTemplate 的拦截器, 拦截后调用 LoadBalancerClient 修改 HttpRequest 对象 ( 主要是 url ) , 且传入调用 LoadBalancerRequestFactory 生成的回调给 LoadBalancerClient。
- RestTemplateCustomizer:为 restTemplate 加上一个拦截器 ( 也可以干点别的, 默认就这一个用处 ) 。
- SmartInitializingSingleton:容器初始化是,调用 RestTemplateCustomizer 为容器中所有加了。@LoadBalanced 的 RestTemplate 加上一个拦截器。
- ReactiveLoadBalancer:定义了 choose 方法, 即如何选取一个 ServiceInstance, 如轮播, 随机。
3.3.2 源码部分
\
- LoadBalancerInterceptor
RestTemplate 的拦截器, 拦截后调用 LoadBalancerClient 修改 HttpRequest 对象 ( 主要是 url ) , 且传入调用 LoadBalancerRequestFactory 生成的回调给 LoadBalancerClient。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
- BlockingLoadBalancerClient
用于根据 serviceId 选取一个 ServiceInstance, 执行从 LoadBalancerRequestFactory 获得的那个回调,发送HTTP请求,远程调用REST ful API。代码如下所示。
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = getHint(serviceId);
LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
new DefaultRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, lbRequest);
}
choose方法,调用RoundRobinLoadBalancer的choose方法,以轮播方式获取一个serviceInstance。代码如下所示。
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
- RoundRobinLoadBalancer
RoundRobinLoadBalancer的choose方法,以轮播方式获取一个serviceInstance。代码如下所示。
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
3.4 Spirng Cloud OpenFeign
\
Feign是一个声明式的HTTP客户端组件,它旨在是编写Http客户端变得更加容易。OpenFeign添加了对于Spring MVC注解的支持,同时集成了Spring Cloud LoadBalancer和Spring Cloud CircuitBreaker,在使用Feign时,提供负载均衡和熔断降级的功能。
3.4.1 入门案例
\
- 添加依赖
在订单工程工程的pom.xml中添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开启Feign功能
使用@EnableFeignClients开启Feign功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 创建Feign客户端
在注解@FeignClient注解中,“cloud-payment-service”是服务名,使用这个名字来从Eureka服务列表中得到相应的服务,来创建LoadBalancer客户端,也可以使用url属性,指定服务的URL。
@FeignClient(value = "cloud-payment-service")
public interface PaymentClient {
@GetMapping("/payment/{id}")
public Payment payment(@PathVariable("id") Integer id);
}
- OrderController
在OrderController中使用FeignClient访问支付服务,代码如下。
@Autowired
private PaymentClient paymentClient;
@GetMapping("/feign/payment/{id}")
public ResponseEntity<Payment> getPaymentByFeign(@PathVariable("id") Integer id) {
Payment payment = paymentClient.payment(id);
return ResponseEntity.ok(payment);
}
- 启动并测试
分别启动支付服务9000端口,9001端口,订单服务,访问http://localhost:9002/order/feign/payment/123,执行效果如图3-4所示。
图3-4 通过Feign调用支付执行效果
\
多次执行发现9000、9001,顺序显示。因此得知Feign集成了负载均衡LoadBalancer组件
3.4.2 超时配置
\
通过分析上述案例的执行现象,得到结论OpenFeign集成了负载均衡组件LoadBalancer,OpenFeign提供了2个超时参数。
- connectTimeout 防止由于服务器处理时间长而阻塞调用者。
- readTimeout 从连接建立时开始应用,在返回响应时间过长时触发。
\
对于所有的FeignClient配置,可以使用"default"假名,代码如下。
feign:
client:
config:
default:
connectTimeout: 5000 #防止由于服务器处理时间长而阻塞调用者
readTimeout: 5000 #从连接建立时开始应用,在返回响应时间过长时触发
如果只对于具体FeignClient配置,可以把default换成具体的FeignClient的名字,代码如下。
feign:
client:
config:
feignName:
connectTimeout: 5000 #防止由于服务器处理时间长而阻塞调用者
readTimeout: 5000 #从连接建立时开始应用,在返回响应时间过长时触发
\
3.4.3 集成熔断器
\
Feign可以集成Spring Cloud CircuitBreaker熔断器,集成后,Feign将使用断路器包装的所有方法。具体用法如下。
- 添加依赖
\
在订单工程pom.xml中增加resilience4j熔断组件依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
- 开启Feign的熔断器支持
在application.yml中增加如下配置。
feign:
circuitbreaker:
enabled: true
\
- Feign熔断降级类
Spring Cloud CircuitBreaker支持降级概念,当熔断器打开,或者调用是出现错误,则执行降级方法。@FeignClient的fallback属性指定讲解的类,注意服务降级类需要在spring容器中注册。代码如下。
package com.lxs.demo.client;
import com.lxs.entity.Payment;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "cloud-payment-service", fallback = PaymentClient.Fallback.class)
public interface PaymentClient {
@GetMapping("/payment/{id}")
public Payment payment(@PathVariable("id") Integer id);
@Component
static class Fallback implements PaymentClient {
@Override
public Payment payment(Integer id) {
Payment payment = new Payment(id, "熔断降级方法返回");
return payment;
}
}
}
\
如果想要获得熔断降级的异常信息,比如打印异常日志,则可以使用fallbackFactory属性指定。代码如下。
package com.lxs.demo.client;
import com.lxs.entity.Payment;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient(name = "cloud-payment-service", fallback = PaymentClient.Fallback.class)
@FeignClient(value = "cloud-payment-service", fallbackFactory = PaymentClient.FallBackFactory.class)
public interface PaymentClient {
@GetMapping("/payment/{id}")
public Payment payment(@PathVariable("id") Integer id);
@Component
static class Fallback implements PaymentClient {
@Override
public Payment payment(Integer id) {
Payment payment = new Payment(id, "熔断降级方法返回");
return payment;
}
}
@Component
static class FallBackFactory implements FallbackFactory<Fallback> {
@Override
public Fallback create(Throwable cause) {
cause.printStackTrace();
return new Fallback();
}
}
}
- 启动并测试
启动订单服务和Eureka,此时因为没有服务提供者支付服务。执行是发生异常,熔断降级方法执行,效果如图3-5所示。
图3-5 Feign熔断降级效果
3.4.4 请求和响应压缩
\
可以使用如下配置对Feign请求和响应进行压缩,代码如下。
feign:
compression:
request:
enabled: true # 请求压缩
mime-types: text/xml,application/xml,application/json # 压缩的类型
min-request-size: 2048 # 请求最小压缩的阈值
response:
enabled: true #响应压缩
useGzipDecoder: true #使用gzip解码器解码响应数据
3.4.5 Feign日志
可以配置打开Feign日志,显示Feign调用的详细信息,比如请求和响应的headers、body和metadata。具体
步骤如下。
- 设置日志级别
Feign Logging只响应debug级别,在application.yml中配置如下。
logging:
level:
com.lxs: debug
- 配置FeignLoggerLevel
在配置类中配置Logger.Level,告诉配置类Feign需要打印的内容,具体代码如下。
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
其中Logger.Level取值如下。
- NONE,无日志记录(默认)。
- BASIC, 只记录请求方法和 URL 以及响应状态码和执行时间。
- HEADERS, 记录基本信息以及请求和响应标头。
- FULL, 记录请求和响应的标头、正文和元数据。
3.4.6 源码解析
\
- 自动配置
FeignAutoConfiguration,启用了两个配置类:FeignClientProperties和FeignHttpClientProperties。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
。。。
}
defaultToProperties就是说默认配置文件的优先级更高,config是一个map,默认的key是default,对所有的feign客户端都生效,可以单独指定一个feign客户端的名字对其做配置。
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
private Map<String, FeignClientConfiguration> config = new HashMap<>();
private String defaultConfig = "default";
}
再看下FeignHttpClientProperties。
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
}
回到FeignAutoConfiguration,configurations是注入系统中所有的FeignClientSpecification,这个类代表的是一个feign客户端,里面有名字和配置类,每一个feign客户端都会有一个与之对应的FeignClientSpecification。
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
当执行FeignContext的setConfiguration()的时候,实际上就是把所有的feign客户端配置类实例保存到configurations成员map中,key是client的名字,value是client本身。
private Map<String, C> configurations = new ConcurrentHashMap<>();
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
这个类还有一个非常核心的方法createContext()。
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//判断configurations是否包含那个client
if (this.configurations.containsKey(name)) {
//如果包含,拿到client的所有的配置类,注册到spring容器
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//注册default配置, 每一个feign客户端的context中都会有默认配置
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//然后注册PropertyPlaceholderAutoConfiguration和defaultConfigType
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
// 然后注册了个PropertySource,名字是feign,
// 里面是有一个kv,key是feign.client.name,value是name,就是feign客户端的名字
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// 这里设置了当前context的父context
context.setParent(this.parent);01
context.setClassLoader(this.parent.getClassLoader());
}
//设置context的名字是FeignContext-feign客户端的名字
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
也就是说每一个feign客户端都有一个AnnotationConfigApplicationContext,这个context里面注册了这个feign客户端的自己的配置类、全局默认的配置类、FeignClientsConfiguration这个配置类、用于占位符解析的配置类,添加了环境变量feign.client.name = feign客户端的名字,设置了父类Context和本Context的名字。
FeignContext构造函数中传递了FeignClientsConfiguration配置类,这个类定义了对于Web解析的组件,源码如下。
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
@Configuration
public class FeignClientsConfiguration {
//这是响应解码器
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
//这是请求编码器
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
//这是解析注解用的Contract
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
//ConversionService
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
// 这是创建HystrixFeign.builder,
// 只有没有Feign.Builder并且启用了hystrix才会创建
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
// 永远不重试
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
。。。
}
- 注册FeignClient
首先从@EnableFeignClient注解开始
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
@Import中FeignClientsRegistrar完成注册,这里面主要就干了2件事,注册默认的配置和注册feign客户端,当然在注册客户端的同时也会注册客户端上的自定义的配置类。先看下registerDefaultConfiguration:
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry)
// 注册默认的配置
registerDefaultConfiguration(metadata, registry);
// 注册feign客户端
registerFeignClients(metadata, registry);
}
}
注册feign客户端本身registerFeignClient。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//实际注册到Spring容器的类型是FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
//设置name
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
//设置contextId
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();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
//primary默认是true,这里设置了primary
beanDefinition.setPrimary(primary);
// 设置alias
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 注册bean到Spring容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
- 注入FeignClient
容器启动的时候,首先是走@EnableFeignClients去注册默认的配置类、注册FeignClient和FeignClient的配置类,然后走@FeignAutoConfiguration,创建FeignContext,把配置类都放进去。当@Autowired注入feign客户端的时候,实际注入的是FactoryBean的getObject()返回的那个类,我们来看下。
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//先去拿到FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//构造builder
Feign.Builder builder = feign(context);
//如果没有配置url,走负载均衡
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 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);
}
//创建target,这里面就是feign自己去创建动态代理了
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
}
FeignContext在FeignAutoConfiguration里面已经注册到容器了,看下是如何构造那个builder的。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
//拿到Feign.Builder
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
//encoder
.encoder(get(context, Encoder.class))
//decoder
.decoder(get(context, Decoder.class))
//contract
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
这个其实就是拿到feign客户端的name对应的那个ApplicationContext,因此,最终就是从这个Context中去拿Type,如果这个Context不存在就去创建,前面已经分析过了,创建的时候回去注册很多配置类,其中有一个FeignClientsConfiguration,这里会创建默认的encoder、decoder一大堆。
\
- 总结
(1)@EnableFeignClients会做bean扫描,向Spring容器注册全局默认配置、FeignClient、FeignClient配置,其中FeignClient不是普通的Bean,而是一个FeignClientFactoryBean
(2)FeignAutoConfiguration向Spring容器注册了FeignContext,FeignContext里面包含了所有的FeignClient,每一个FeignClient都关联了一个ApplicationContext,这个ApplicationContext中就包含了encoder、decoder、contract那些核心组件,SpringMvcContract就是用来解析web相关注解的。
(4)当@Autowired注入FeignClient接口的时候,实际注入的是FeignClientFactoryBean的getObject()返回的bean,在getObject()里面调用了buidler.target()返回了FeignClient实例。
(5)如果在子线程中调用feign接口,需要注意子线程中是无法获取HttpServletRequest的,此时就算是feign接口配置了拦截器,在拦截器里面一样是无法读取到http header的,对于某些使用拦截器统一设置http header的情况尤其要注意,feign说白了就是个http util,仅此而已。