OpenFeign 是一个近程客户端申请代理,它的根本作用是让开发者可能以面向接口的形式来实现近程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示。
在明天的内容中,咱们须要详细分析 OpenFeign 它的工作原理及源码,咱们持续回到这段代码。
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
IGoodsServiceFeignClient goodsServiceFeignClient;
@Autowired
IPromotionServiceFeignClient promotionServiceFeignClient;
@Autowired
IOrderServiceFeignClient orderServiceFeignClient;
/**
* 下单
*/
@GetMapping
public String order(){String goodsInfo=goodsServiceFeignClient.getGoodsById();
String promotionInfo=promotionServiceFeignClient.getPromotionById();
String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
return result;
}
}
从这段代码中,先引出对于 OpenFeign 性能实现的思考。
- 申明
@FeignClient
注解的接口,如何被解析和注入的? - 通过
@Autowired
依赖注入,到底是注入一个什么样的实例 - 基于 FeignClient 申明的接口被解析后,如何存储?
- 在发动办法调用时,整体的工作流程是什么样的?
- OpenFeign 是如何集成 Ribbon 做负载平衡解析?
带着这些疑难,开始去逐项剖析 OpenFeign 的外围源码
OpenFeign 注解扫描与解析
思考,一个被申明了
@FeignClient
注解的接口,应用@Autowired
进行依赖注入,而最终这个接口可能失常被注入实例。
从这个后果来看,能够失去两个论断
- 被
@FeignClient
申明的接口,在 Spring 容器启动时,会被解析。 - 因为被 Spring 容器加载的是接口,而接口又没有实现类,因而 Spring 容器解析时,会生成一个动静代理类。
EnableFeignClient
@FeignClient
注解是在什么时候被解析的呢?基于咱们之前所有积攒的常识,无非就以下这几种
- ImportSelector,批量导入 bean
- ImportBeanDefinitionRegistrar,导入 bean 申明并进行注册
- BeanFactoryPostProcessor,一个 bean 被装载的前后处理器
在这几个选项中,仿佛 ImportBeanDefinitionRegistrar
更适合,因为第一个是批量导入一个 bean 的 string 汇合,不适宜做动静 Bean 的申明。而 BeanFactoryPostProcessor
是一个 Bean 初始化之前和之后被调用的处理器。
而在咱们的 FeignClient 申明中,并没有 Spring 相干的注解,所以天然也不会被 Spring 容器加载和触发。
那么
@FeignClient
是在哪里被申明扫描的呢?
在集成 FeignClient 时,咱们在 SpringBoot 的 main 办法中,申明了一个注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")
。这个注解须要填写一个指定的包名。
嗯,看到这里,基本上就能猜测出,这个注解必然和 @FeignClient
注解的解析有莫大的关系。
上面这段代码是 @EnableFeignClients
注解的申明,果然看到了一个很相熟的脸孔FeignClientsRegistrar
。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
FeignClientsRegistrar
FeignClientRegistrar,次要性能就是针对申明 @FeignClient
注解的接口进行扫描和注入到 IOC 容器。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}
果然,这个类实现了 ImportBeanDefinitionRegistrar
接口
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}
这个接口有两个重载的办法,用来实现 Bean 的申明和注册。
简略给大家演示一下 ImportBeanDefinitionRegistrar 的作用。
在
gpmall-portal
这个我的项目的com.gupaoedu
目录下,别离创立
- HelloService.java
- GpImportBeanDefinitionRegistrar.java
- EnableGpRegistrar.java
- TestMain
- 定义一个须要被装载到 IOC 容器中的类 HelloService
public class HelloService {}
- 定义一个 Registrar 的实现,定义一个 bean,装载到 IOC 容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {BeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {}
- 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain {public static void main(String[] args) {ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class));
}
}
- 通过后果演示能够发现,
HelloService
这个 bean 曾经装载到了 IOC 容器。
这就是动静装载的性能实现,它相比于 @Configuration 配置注入,会多了很多的灵活性。ok,再回到 FeignClient 的解析中来。
FeignClientsRegistrar.registerBeanDefinitions
registerDefaultConfiguration
办法外部从SpringBoot
启动类上查看是否有@EnableFeignClients
, 有该注解的话,则实现Feign
框架相干的一些配置内容注册。registerFeignClients
办法外部从classpath
中,扫描取得@FeignClient
润饰的类,将类的内容解析为BeanDefinition
, 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition
将解析解决过的FeignClient BeanDeifinition
增加到spring
容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册 @EnableFeignClients 中定义 defaultConfiguration 属性下的类,包装成 FeignClientSpecification,注册到 Spring 容器。// 在 @FeignClient 中有一个属性:configuration,这个属性是示意各个 FeignClient 自定义的配置类,前面也会通过调用 registerClientConfiguration 办法来注册成 FeignClientSpecification 到容器。// 所以,这里能够齐全了解在 @EnableFeignClients 中配置的是做为兜底的配置,在各个 @FeignClient 配置的就是自定义的状况。registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
这外面须要重点剖析的就是
registerFeignClients
办法,这个办法次要是扫描类门路下所有的@FeignClient
注解,而后进行动静 Bean 的注入。它最终会调用registerFeignClient
办法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {registerFeignClient(registry, annotationMetadata, attributes);
}
FeignClientsRegistrar.registerFeignClients
registerFeignClients 办法的定义如下。
//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取 @EnableFeignClients 注解的元数据
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 获取 @EnableFeignClients 注解中的 clients 属性,能够配置 @FeignClient 申明的类,如果配置了,则须要扫描并加载。final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 默认 TypeFilter 失效,这种模式会查问出许多不合乎你要求的 class 名
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); // 增加蕴含过滤的属性 @FeignClient。Set<String> basePackages = getBasePackages(metadata); // 从 @EnableFeignClients 注解中获取 basePackages 配置。for (String basePackage : basePackages) {//scanner.findCandidateComponents(basePackage) 扫描 basePackage 下的 @FeignClient 注解申明的接口
candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); // 增加到 candidateComponents,也就是候选容器中。}
}
else {// 如果配置了 clients,则须要增加到 candidateComponets 中。for (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍历候选容器列表。for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) { // 如果属于 AnnotatedBeanDefinition 实例类型
// verify annotated class is an interface
// 失去 @FeignClient 注解的 beanDefinition
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); // 获取这个 bean 的注解元数据
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 获取元数据属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 获取 @FeignClient 中配置的服务名称。String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
FeignClient Bean 的注册
这个办法就是把 FeignClient 接口注册到 Spring IOC 容器,
FeignClient 是一个接口,那么这里注入到 IOC 容器中的对象是什么呢?
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName(); // 获取 FeignClient 接口的类全门路
Class clazz = ClassUtils.resolveClassName(className, null); // 加载这个接口,失去 Class 实例
// 构建 ConfigurableBeanFactory,提供 BeanFactory 的配置能力
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
// 获取 contextId
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 构建一个 FeignClient FactoryBean,这个是工厂 Bean。FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
// 设置工厂 Bean 的相干属性
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//BeanDefinitionBuilder 是用来构建 BeanDefinition 对象的建造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
// 把 @FeignClient 注解配置中的属性设置到 FactoryBean 中。factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject(); //factoryBean.getObject(),基于工厂 bean 发明一个 bean 实例。});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 设置注入模式,采纳类型注入
definition.setLazyInit(true); // 设置提早华
validate(attributes);
// 从 BeanDefinitionBuilder 中构建一个 BeanDefinition,它用来形容一个 bean 的实例定义。AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] {contextId + "FeignClient"};
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); // 把 BeanDefinition 的这个 bean 定义注册到 IOC 容器。}
综上代码剖析,其实实现逻辑很简略。
- 创立一个 BeanDefinitionBuilder。
- 创立一个工厂 Bean,并把从 @FeignClient 注解中解析的属性设置到这个 FactoryBean 中
- 调用 registerBeanDefinition 注册到 IOC 容器中
对于 FactoryBean
在上述的逻辑中,咱们重点须要关注的是 FactoryBean,这个大家可能接触得会比拟少一点。
首先,须要留神的是 FactoryBean 和 BeanFactory 是不一样的,FactoryBean 是一个工厂 Bean,能够生成某一个类型 Bean 实例,它最大的一个作用是:能够让咱们自定义 Bean 的创立过程。
而,BeanFactory 是 Spring 容器中的一个根本类也是很重要的一个类,在 BeanFactory 中能够创立和治理 Spring 容器中的 Bean。
上面这段代码是 FactoryBean 接口的定义。
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {return true;}
}
从下面的代码中咱们发现在 FactoryBean 中定义了一个 Spring Bean 的很重要的三个个性:是否单例、Bean 类型、Bean 实例,这也应该是咱们对于 Spring 中的一个 Bean 最直观的感触。
FactoryBean 自定义应用
上面咱们来模仿一下 @FeignClient 解析以及工厂 Bean 的构建过程。
- 先定义一个接口,这个接口能够类比为咱们下面形容的 FeignClient.
public interface IHelloService {String say();
}
- 接着,定义一个工厂 Bean,这个工厂 Bean 中次要是针对 IHelloService 生成动静代理。
public class DefineFactoryBean implements FactoryBean<IHelloService> {
@Override
public IHelloService getObject() {IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {System.out.println("begin execute");
return "Hello FactoryBean";
});
return helloService;
}
@Override
public Class<?> getObjectType() {return IHelloService.class;}
}
- 通过实现 ImportBeanDefinitionRegistrar 这个接口,来动静注入 Bean 实例
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {DefineFactoryBean factoryBean=new DefineFactoryBean();
BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(IHelloService.class,()-> factoryBean.getObject());
BeanDefinition beanDefinition=definition.getBeanDefinition();
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- 申明一个注解,用来示意动静 bean 的注入导入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {}
- 编写测试类,测试 IHelloService 这个接口的动静注入
@Configuration
@EnableGpRegistrar
public class TestMain {public static void main(String[] args) {ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
IHelloService is=applicationContext.getBean(IHelloService.class);
System.out.println(is.say());
}
}
- 运行上述的测试方法,能够看到 IHelloService 这个接口,被动静代理并且注入到了 IOC 容器。
FeignClientFactoryBean
由上述案例可知,Spring IOC 容器在注入 FactoryBean 时,会调用 FactoryBean 的 getObject()办法取得 bean 的实例。因而咱们能够依照这个思路去剖析 FeignClientFactoryBean
@Override
public Object getObject() {return getTarget();
}
构建对象 Bean 的实现代码如下,这个代码的实现较长,咱们分为几个步骤来看
Feign 上下文的构建
先来看上下文的构建逻辑,代码局部如下。
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
}
两个要害的对象阐明:
- FeignContext 是全局惟一的上下文,它继承了 NamedContextFactory,它是用来来对立保护 feign 中各个 feign 客户端互相隔离的上下文,FeignContext 注册到容器是在 FeignAutoConfiguration 上实现的,代码如下!
//FeignAutoConfiguration.java
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
在初始化 FeignContext 时,会把 configurations
放入到 FeignContext 中。configuration
示意以后被扫描到的所有 @FeignClient。
- Feign.Builder 用来构建 Feign 对象,基于 builder 实现上下文信息的构建,代码如下。
protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(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)); //contract 协定,用来实现模版解析(前面再详细分析)// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
从代码中能够看到,feign
办法,次要是针对不同的服务提供者生成 Feign 的上下文信息,比方 logger
、encoder
、decoder
等。因而,从这个剖析过程中,咱们不难猜测到它的原理构造,如下图所示
父子容器隔离的实现形式如下,当调用 get 办法时,会从 context
中去获取指定 type 的实例对象。
//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type" + type + "for" + contextId);
}
return instance;
}
接着,调用 NamedContextFactory
中的 getInstance
办法。
//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
// 依据 `name` 获取容器上下文
AnnotationConfigApplicationContext context = this.getContext(name);
try {
// 再从容器上下文中获取指定类型的 bean。return context.getBean(type);
} catch (NoSuchBeanDefinitionException var5) {return null;}
}
getContext
办法依据 name
从contexts
容器中取得上下文对象,如果没有,则调用 createContext
创立。
protected AnnotationConfigApplicationContext getContext(String name) {if (!this.contexts.containsKey(name)) {synchronized(this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, this.createContext(name));
}
}
}
return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
生成动静代理
第二个局部,如果 @FeignClient 注解中,没有配置url
,也就是不走相对申请门路,则执行上面这段逻辑。
因为咱们在 @FeignClient 注解中应用的是name
,所以须要执行负载平衡策略的分支逻辑。
<T> T getTarget() {
// 省略.....
if (!StringUtils.hasText(url)) { // 是 @FeignClient 中的一个属性,能够定义申请的相对 URL
if (LOG.isInfoEnabled()) {
LOG.info("For'" + name
+ "'URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {url = "http://" + name;}
else {url = name;}
url += cleanPath();
//
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
// 省略....
}
loadBalance 办法的代码实现如下,其中 Client
是 Spring Boot 主动拆卸的时候实现的,如果替换了其余的 http 协定框架,则 client 则对应为配置的协定 api。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//Feign 发送申请以及承受响应的 http client,默认是 Client.Default 的实现,能够批改成 OkHttp、HttpClient 等。Client client = getOptional(context, Client.class);
if (client != null) {builder.client(client); // 针对以后 Feign 客户端,设置网络通信的 client
//targeter 示意 HystrixTarger 实例,因为 Feign 能够集成 Hystrix 实现熔断,所以这里会一层包装。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 or spring-cloud-starter-loadbalancer?");
}
HystrixTarget.target
代码如下,咱们没有集成 Hystrix,因而不会触发 Hystrix 相干的解决逻辑。
//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { // 没有配置 Hystrix,则走这部分逻辑
return feign.target(target);
}
// 省略....
return feign.target(target);
}
进入到 Feign.target
办法,代码如下。
//Feign.java
public <T> T target(Target<T> target) {return this.build().newInstance(target); //target.HardCodedTarget
}
public Feign build() {
// 这里会构建一个 LoadBalanceClient
Client client = (Client)Capability.enrich(this.client, this.capabilities);
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
//OpenFeign Log 配置
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
// 模版解析协定(这个接口十分重要:它决定了哪些注解能够标注在接口 / 接口办法上是无效的,并且提取出无效的信息,组装成为 MethodMetadata 元信息。)Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
// 封装 Request 申请的 连贯超时 = 默认 10s,读取超时 = 默认 60
Options options = (Options)Capability.enrich(this.options, this.capabilities);
// 编码器
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
// 解码器
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
//synchronousMethodHandlerFactory,同步办法调用处理器(很重要,后续要用到)Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
//ParseHandlersByName 的作用就是咱们传入 Target(封装了咱们的模仿接口,要拜访的域名),返回这个接口下的各个办法,对应的执行 HTTP 申请须要的一系列信息
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
build
办法中,返回了一个 ReflectiveFeign
的实例对象,先来看 ReflectiveFeign
中的 newInstance
办法。
public <T> T newInstance(Target<T> target) {// 润饰了 @FeignClient 注解的接口办法封装成办法处理器,把指定的 target 进行解析,失去须要解决的办法汇合。Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); // 定义一个用来保留须要解决的办法的汇合 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //JDK8 当前,接口容许默认办法实现,这里是对默认办法进行封装解决。List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // 遍历 @FeignClient 接口的所有办法 for (Method method : target.type().getMethods()) {// 如果是 Object 中的办法,则间接跳过 if (method.getDeclaringClass() == Object.class) {continue;} else if (Util.isDefault(method)) {// 如果是默认办法,则把该办法绑定一个 DefaultMethodHandler。DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else {// 否则,增加 MethodHandler(SynchronousMethodHandler)。methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } // 创立动静代理类。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;}
上述代码,其实也不难理解。
-
解析 @FeignClient 接口申明的办法,依据不同办法绑定不同的处理器。
- 默认办法,绑定 DefaultMethodHandler
- 近程办法,绑定 SynchronousMethodHandler
- 应用 JDK 提供的 Proxy 创立动静代理
MethodHandler,会把办法参数、办法返回值、参数汇合、申请类型、申请门路进行解析存储,如下图所示。
FeignClient 接口解析
接口解析也是 Feign 很重要的一个逻辑,它能把接口申明的属性转化为 HTTP 通信的协定参数。
执行逻辑 RerlectiveFeign.newInstance
public <T> T newInstance(Target<T> target) {Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //here}
targetToHandlersByName.apply(target); 会解析接口办法上的注解,从而解析出办法粒度的特定的配置信息,而后生产一个 SynchronousMethodHandler
而后须要保护一个 <method,MethodHandler> 的 map,放入 InvocationHandler 的实现 FeignInvocationHandler 中。
public Map<String, MethodHandler> apply(Target target) {List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : metadata) {BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() != null) {buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else {buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target); } if (md.isIgnored()) {result.put(md.configKey(), args -> {throw new IllegalStateException(md.configKey() + "is not a method handled by feign"); }); } else {result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result;}
为了更好的了解上述逻辑,咱们能够借助上面这个图来了解!
阶段性小结
通过上述过程剖析,被申明为 @FeignClient 注解的类,在被注入时,最终会生成一个动静代理对象 FeignInvocationHandler。
当触发办法调用时,会被 FeignInvocationHandler#invoke 拦挡,FeignClientFactoryBean 在实例化过程中所做的事件如下图所示。
总结来说就几个点:
- 解析 Feign 的上下文配置,针对以后的服务实例构建容器上下文并返回 Feign 对象
- Feign 依据高低围配置把 log、encode、decoder、等配置项设置到 Feign 对象中
- 对指标服务,应用 LoadBalance 以及 Hystrix 进行包装
- 通过 Contract 协定,把 FeignClient 接口的申明,解析成 MethodHandler
- 遍历 MethodHandler 列表,针对须要近程通信的办法,设置
SynchronousMethodHandler
处理器,用来实现同步近程调用。 - 应用 JDK 中的动静代理机制构建动静代理对象。
近程通信实现
在 Spring 启动过程中,把所有的筹备工作准备就绪后,就开始执行近程调用。
在后面的剖析中,咱们晓得 OpenFeign 最终返回的是一个 #ReflectiveFeign.FeignInvocationHandler 的对象。
那么当客户端发动申请时,会进入到 FeignInvocationHandler.invoke 办法中,这个大家都晓得,它是一个动静代理的实现。
//FeignInvocationHandler.java@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) {return false;} } else if ("hashCode".equals(method.getName())) {return hashCode(); } else if ("toString".equals(method.getName())) {return toString(); } return dispatch.get(method).invoke(args);}
接着,在 invoke 办法中,会调用 this.dispatch.get(method)).invoke(args)
。this.dispatch.get(method)
会返回一个 SynchronousMethodHandler, 进行拦挡解决。
this.dispatch,其实就是在初始化过程中创立的,private final Map<Method, MethodHandler> dispatch;
实例。
SynchronousMethodHandler.invoke
这个办法会依据参数生成实现的 RequestTemplate 对象,这个对象是 Http 申请的模版,代码如下,代码的实现如下:
@Override
public Object invoke(Object[] argv) throws Throwable { //argv,示意调用办法传递的参数。RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv); // 获取配置项,连贯超时工夫、近程通信数据获取超时工夫
Retryer retryer = this.retryer.clone(); // 获取重试策略
while (true) {
try {return executeAndDecode(template, options);
} catch (RetryableException e) {
try {retryer.continueOrPropagate(e);
} catch (RetryableException th) {Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}
}
if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
上述代码的执行流程中,须要先结构一个 Request 对象,而后调用 client.execute 办法执行网络通信申请,整体实现流程如下。
executeAndDecode
通过上述的代码,咱们曾经将 RequestTemplate 拼装实现,下面的代码中有一个 executeAndDecode()
办法,该办法通过 RequestTemplate 生成 Request 申请对象,而后利用 Http Client 获取 response,来获取响应信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template); // 把 template 转化为申请报文
if (logLevel != Logger.Level.NONE) { // 设置日志 level
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 发动申请,此时 client 是 LoadBalanceFeignClient,须要先对服务名称进行解析负载
response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 12 // 获取返回后果
response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null) // 如果设置了解码器,这须要对响应数据做解码
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response, metadata.returnType(), elapsedTime);
try {if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();} catch (CompletionException e) {Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
LoadBalanceClient
@Overridepublic
Response execute(Request request, Request.Options options) throws IOException {
try {URI asUri = URI.create(request.url()); // 获取申请 uri,此时的地址还未被解析。String clientName = asUri.getHost(); // 获取 host,实际上就是服务名称
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); // 加载客户端的配置信息
IClientConfig requestConfig = getClientConfig(options, clientName); // 创立负载平衡客户端(FeignLoadBalancer),执行申请
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();} catch (ClientException e) {IOException io = findIOException(e);
if (io != null) {throw io;}
throw new RuntimeException(e);
}
}
从下面的代码能够看到,lbClient(clientName) 创立了一个负载平衡的客户端,它实际上就是生成的如下所述的类:
public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
整体总结
OpenFeign 的整体通信原理解析,如下图所示。
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自
Mic 带你学架构
!
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!