共计 22597 个字符,预计需要花费 57 分钟才能阅读完成。
后面曾经学习了两个 Spring Cloud 组件:
- Eureka:实现服务注册性能;
- Ribbon:提供基于 RestTemplate 的 HTTP 客户端并且反对服务负载平衡性能。
通过这两个组件咱们临时能够实现服务注册和可配置负载平衡的服务调用。明天咱们要学习的是 Feign,那么 Feign 解决了什么问题呢?
绝对于 Eureka,Ribbon 来说,Feign 的位置如同不是那么重要,Feign 是一个申明式的 REST 客户端,它的目标就是让 REST 调用更加简略。通过提供 HTTP 申请模板,让 Ribbon 申请的书写更加简略和便捷。另外,在 Feign 中整合了 Ribbon,从而不须要显式的申明 Ribbon 的 jar 包。
后面在应用 Ribbon+RestTemplate 时,利用 RestTemplate 对 http 申请的封装解决,造成了一套模版化的调用办法。然而在理论开发中,因为对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign 在此基础上做了进一步封装,由他来帮忙咱们定义和实现依赖服务接口的定义。在 Feign 的实现下,咱们只需创立一个接口并应用注解的形式来配置它(以前是 Dao 接口下面标注 Mapper 注解, 当初是一个微服务接口下面标注一个 Feign 注解即可),即可实现对服务提供方的接口绑定,简化了应用 Spring Cloud Ribbon 时,主动封装服务调用客户端的开发量。
Feign 的应用:#
1. 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
另外,咱们须要增加_spring-cloud-dependencies_:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rickiyang.learn</groupId>
<artifactId>feign-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>feign-consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 接下来,咱们须要在主类中增加 _@EnableFeignClients_:
package com.rickiyang.learn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class FeignConsumerApplication {public static void main(String[] args) {SpringApplication.run(FeignConsumerApplication.class, args);
}
}
3. 再来看一下用 Feign 写的 HTTP 申请的格局:
import com.rickiyang.learn.entity.Person;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* @author: rickiyang
* @date: 2019/10/5
* @description:
*/
@FeignClient(name= "eureka-client")
public interface HelloRemote {@RequestMapping(value = "/hello/{name}")
String hello(@PathVariable(value = "name") String name);
@PostMapping(value ="/add",produces = "application/json; charset=UTF-8")
String addPerson(@RequestBody Person person);
@GetMapping("/getPerson/{id}")
String getPerson(@PathVariable("id") Integer id);
}
用 FeignClient 注解申明要调用的服务是哪个,该服务中的办法都有咱们常见的 Restful 形式的 API 来申明,这种形式大家是不是感觉像是在写 Restful 接口一样。
代码示例:点击这里。
note:
示例代码的正确打开方式:先启动服务端,而后启动一个 client 端,再次启动 feign-consumer,调用 feign-consumer 中的接口即可。
还记得在 Ribbon 学习的时候应用 RestTemplate 发动 HTTP 申请的形式吗:
restTemplate.getForEntity("http://eureka-client/hello/" + name, String.class).getBody();
将整个的申请 URL 和参数都放在一起,尽管没有什么问题,总归不是那么优雅。应用 Feign 之后你能够应用 Restful 形式进行调用,写起来也会更加清晰。
Feign 调用过程剖析 #
下面简略介绍了 Feign 的应用形式,大家能够联合着代码示例运行一下,理解根本的应用形式。接下来咱们一起剖析 Feign 的调用过程,咱们带着两个问题去跟踪:
1. 申请如何被 Feign 对立托管;
2.Feign 如何散发申请。
这两个问题应该就涵盖了 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;
/**
* Scans for interfaces that declare they are feign clients (via {@link FeignClient
* <code>@FeignClient</code>}). Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration
* <code>@Configuration</code>} classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
// 等价于 basePackages 属性,更简洁的形式
String[] value() default {};
// 指定多个包名进行扫描
String[] basePackages() default {};
// 指定多个类或接口的 class, 扫描时会在这些指定的类和接口所属的包进行扫描
Class<?>[] basePackageClasses() default {};
// 为所有的 Feign Client 设置默认配置类
Class<?>[] defaultConfiguration() default {};
// 指定用 @FeignClient 正文的类列表。如果该项配置不为空,则不会进行类门路扫描
Class<?>[] clients() default {};}
正文上说了该注解用于扫描 FeignClient 申明的类。咱们用 FeignClient 注解去申明一个 Eureka 客户端,那么猜测这里应该是取到咱们申明的 Eureka client 名称,而后去拜访 Eureka server 获取服务提供者。
同样的,为所有 Feign Client 也反对文件属性的配置,如下 :
feign:
client:
config:
# 默认为所有的 feign client 做配置(留神和上例 github-client 是同级的)
default:
connectTimeout: 5000 # 连贯超时工夫
readTimeout: 5000 # 读超时工夫设置
注 : 如果通过 Java 代码进行了配置,又通过配置文件进行了配置,则配置文件的中的 Feign 配置会笼罩 Java 代码的配置。
但也能够设置 feign.client.defalult-to-properties=false,禁用掉 feign 配置文件的形式让 Java 配置失效。
留神到类头申明的 @Import 注解援用的 FeignClientsRegistrar 类,这个类的作用是在 EnableFeignClients 初始化的时候扫描该注解对应的配置。
接着看 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 {
// 指定 Feign Client 的名称,如果我的项目应用了 Ribbon,name 属性会作为微服务的名称,用于服务发现
@AliasFor("name")
String value() default "";
// 用 serviceId 做服务发现曾经被废除,所以不举荐应用该配置
@Deprecated
String serviceId() default "";
// 指定 Feign Client 的 serviceId,如果我的项目应用了 Ribbon,将应用 serviceId 用于服务发现, 但下面能够看到 serviceId 做服务发现曾经被废除,所以也不举荐应用该配置
@AliasFor("value")
String name() default "";
// 为 Feign Client 新增注解 @Qualifier
String qualifier() default "";
// 申请地址的相对 URL,或者解析的主机名
String url() default "";
// 调用该 feign client 产生了常见的 404 谬误时,是否调用 decoder 进行解码异样信息返回, 否则抛出 FeignException
boolean decode404() default false;
//Feign Client 设置默认配置类
Class<?>[] configuration() default {};
// 定义容错的解决类,当调用近程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。实现的法办法即对应接口的容错解决逻辑
Class<?> fallback() default void.class;
// 工厂类,用于生成 fallback 类示例,通过这个属性咱们能够实现每个接口通用的容错逻辑,缩小反复的代码
Class<?> fallbackFactory() default void.class;
// 定义以后 FeignClient 的所有办法映射加对立前缀
String path() default "";
// 是否将此 Feign 代理标记为一个 Primary Bean,默认为 ture
boolean primary() default true;}
同样在 FeignClientsRegistrar 类中也会去扫描 FeignClient 注解对应的配置信息。咱们间接看 FeignClientsRegistrar 的逻辑:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}
// 在这个重载的办法外面做了两件事件://1. 将 EnableFeignClients 注解对应的配置属性注入
//2. 将 FeignClient 注解对应的属性注入
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注入 EnableFeignClients 注解对应的配置属性
registerDefaultConfiguration(metadata, registry);
// 注入 FeignClient 注解对应的属性
registerFeignClients(metadata, registry);
}
/**
* 拿到 EnableFeignClients 注解 defaultConfiguration 字段的值
* 而后进行注入
*
**/
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) {
// 获取 ClassPath 扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 为扫描器设置资源加载器
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 1. 从 @EnableFeignClients 注解中获取到配置的各个属性值
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 2. 注解类型过滤器,只过滤 @FeignClient
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// 3. 从 1. 中的属性值中获取 clients 属性的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 扫描器设置过滤器且获取须要扫描的根底包汇合
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}else {
// clients 属性值不为 null,则将其 clazz 门路转为包门路
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)));
}
// 3. 扫描根底包,且满足过滤条件下的接口封装成 BeanDefinition
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
// 遍历扫描到的 bean 定义
for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 并校验扫描到的 bean 定义类是一个接口
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 获取 @FeignClient 注解上的各个属性值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 能够看到这里也注册了一个 FeignClient 的配置 bean
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册 bean 定义到 spring 中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
/**
* 注册 bean
**/
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 1. 获取类名称,也就是本例中的 FeignService 接口
String className = annotationMetadata.getClassName();
// 2.BeanDefinitionBuilder 的次要作用就是构建一个 AbstractBeanDefinition
// AbstractBeanDefinition 类最终被构建成一个 BeanDefinitionHolder
// 而后注册到 Spring 中
// 留神:beanDefinition 类为 FeignClientFactoryBean,故在 Spring 获取类的时候理论返回的是
// FeignClientFactoryBean 类
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// 3. 增加 FeignClientFactoryBean 的属性,// 这些属性也都是咱们在 @FeignClient 中定义的属性
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"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 4. 设置别名 name 就是咱们在 @FeignClient 中定义的 name 属性
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;}
// 5. 定义 BeanDefinitionHolder,// 在本例中 名称为 FeignService,类为 FeignClientFactoryBean
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] {alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
private void validate(Map<String, Object> attributes) {AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", FeignClient.class, null);
Assert.isTrue(!annotation.getClass("fallback").isInterface(),
"Fallback class must implement the interface annotated by @FeignClient"
);
Assert.isTrue(!annotation.getClass("fallbackFactory").isInterface(),
"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
);
}
......
......
......
}
在这里做了两件事件:
- 将 EnableFeignClients 注解对应的配置属性注入;
- 将 FeignClient 注解对应的属性注入。
生成 FeignClient 对应的 bean,注入到 Spring 的 IOC 容器。
在 registerFeignClient 办法中结构了一个 BeanDefinitionBuilder 对象,BeanDefinitionBuilder 的次要作用就是构建一个 AbstractBeanDefinition,AbstractBeanDefinition 类最终被构建成一个 BeanDefinitionHolder 而后注册到 Spring 中。
beanDefinition 类为 FeignClientFactoryBean,故在 Spring 获取类的时候理论返回的是 FeignClientFactoryBean 类。
FeignClientFactoryBean
作为一个实现了 FactoryBean
的工厂类,那么每次在 Spring Context 创立实体类的时候会调用它的 getObject()
办法。
public Object getObject() throws Exception {FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
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));
}
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));
}
这里的 getObject()
其实就是将 @FeinClient
中设置 value 值进行组装起来,此时或者会有疑难,因为在配置 FeignClientFactoryBean
类时特意说过并没有将 Configuration 传过来,那么 Configuration 中的属性是如何配置的呢?看其第一句是:
FeignContext context = applicationContext.getBean(FeignContext.class);
从 Spring 容器中获取 FeignContext.class
的类,咱们能够看下这个类是从哪加载的。点击该类查看被援用的中央,能够找到在 FeignAutoConfiguration
类中有申明 bean:
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
......
......
......
}
从下面的代码中能够看到在 set 属性的时候将 FeignClientSpecification 类型的类全副退出此类的属性中。还记得在下面剖析 registerFeignClients
办法的时候外面有一行代码调用:registerClientConfiguration()
办法:
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
在注册 BeanDefinition 的时候,configuration 其实也被作为参数,传给了 FeignClientSpecification。所以这时候在 FeignContext 中是带着 configuration 配置信息的。
至此咱们曾经实现了配置属性的拆卸工作,那么是如何执行的呢?咱们能够看 getObject()
最初一句能够看到返回了 Targeter.target
的办法。
return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
那么这个 Targeter
是哪来的?咱们还是看下面的 FeignAutoConfiguration
类,能够看到其中有两个 Targeter
类,一个是 DefaultTargeter
,一个是HystrixTargeter
。当配置了feign.hystrix.enabled = true
的时候,Spring 容器中就会配置 HystrixTargeter
此类,如果为 false 那么 Spring 容器中配置的就是DefaultTargeter
。
咱们以 DefaultTargeter
为例介绍一下接下来是如何通过创立代理对象的:
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {return feign.target(target);
}
}
public static class Builder {public <T> T target(Target<T> target) {return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}`
在 target 办法中有个参数:Feign.Builder:Copy
`public static class Builder {
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
......
......
......
}
构建 feign.builder 时会向 FeignContext 获取配置的 Encoder,Decoder 等各种信息。Builder 中的参数来自于配置文件的 feign.client.config 外面的属性。
查看 ReflectiveFeign
类中 newInstance
办法是返回一个代理对象:
public <T> T newInstance(Target<T> target) {
// 为每个办法创立一个 SynchronousMethodHandler 对象,并放在 Map 外面
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {continue;} else if(Util.isDefault(method)) {
// 如果是 default 办法,阐明曾经有实现了,用 DefaultHandler
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// 否则就用下面的 SynchronousMethodHandler
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 设置拦截器
// 创立动静代理,factory 是 InvocationHandlerFactory.Default,创立进去的是
// ReflectiveFeign.FeignInvocationHanlder,也就是说后续对办法的调用都会进入到该对象的 inovke 办法
InvocationHandler handler = factory.create(target, methodToHandler);
// 创立动静代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);
}
return proxy;
}`
这个办法大略的逻辑是:1. 依据 target,解析生成 `MethodHandler` 对象;2. 对 `MethodHandler` 对象进行分类整理,整顿成两类:default 办法和 SynchronousMethodHandler 办法;3. 通过 jdk 动静代理生成代理对象,这里是最要害的中央;4. 将 `DefaultMethodHandler` 绑定到代理对象。最终都是执行了 `SynchronousMethodHandler` 拦截器中的 `invoke` 办法:Copy
`@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;
}
}
}
invoke
办法办法首先生成 RequestTemplate 对象,利用 encoder,decoder 以及 retry 等配置,上面有一个死循环调用:executeAndDecode,从名字上看就是执行调用逻辑并对返回后果解析。
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 依据 RequestTemplate 生成 Request 对象
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {// 调用 client 对象的 execute()办法执行 http 调用逻辑,
//execute()外部可能设置 request 对象,也可能不设置,所以须要 response.toBuilder().request(request).build(); 这一行代码
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
// IOException 的时候,包装成 RetryableException 异样, 下面的 while 循环 catch 里捕获的就是这个异样
throw errorExecuting(request, e);
}
// 统计 执行调用破费的工夫
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();}
// 如果元数据返回类型是 Response,间接返回回去即可,不须要 decode()解码
if (Response.class == metadata.returnType()) {if (response.body() == null) {return response;}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();}
// 次要对 2xx 和 404 等进行解码,404 须要特地的开关管制。其余状况,应用 errorDecoder 进行解码,以异样的形式返回
if (response.status() >= 200 && response.status() < 300) {if (void.class == metadata.returnType()) {return null;} else {return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {return decode(response);
} else {throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {if (shouldClose) {ensureClosed(response.body());
}
}
}
这里次要就是应用:client.execute(request, options) 来发动调用,上面根本都是解决返回后果的逻辑。到此咱们的整个调用生态曾经解析结束。
咱们能够整顿一下下面的剖析:
首先调用接口为什么会间接发送申请?
起因就是 Spring 扫描了 @FeignClient
注解,并且依据配置的信息生成代理类,调用的接口实际上调用的是生成的代理类。
其次申请是如何被 Feign 接管的?
- Feign 通过扫描
@EnableFeignClients
注解中配置包门路,扫描@FeignClient
注解并将注解配置的信息注入到 Spring 容器中,类型为FeignClientFactoryBean
; - 而后通过
FeignClientFactoryBean
的getObject()
办法失去不同动静代理的类并为每个办法创立一个SynchronousMethodHandler
对象; - 为每一个办法创立一个动静代理对象,动静代理的实现是
ReflectiveFeign.FeignInvocationHanlder
,代理被调用的时候,会依据以后调用的办法,转到对应的SynchronousMethodHandler
。
这样咱们收回的申请就可能被曾经配置好各种参数的 Feign handler 进行解决,从而被 Feign 托管。
申请如何被 Feign 散发的?
上一个问题曾经答复了 Feign 将每个办法都封装成为代理对象,那么当该办法被调用时,真正执行逻辑的是封装好的代理对象进行解决,执行对应的服务调用逻辑。
作者:rickiyang
出处:https://www.cnblogs.com/rickiyang/p/11802487.html
本站应用「CC BY 4.0」创作共享协定,转载请在文章显著地位注明作者及出处。