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功能实现的思考。

  1. 声明@FeignClient注解的接口,如何被解析和注入的?
  2. 通过@Autowired依赖注入,到底是注入一个什么样的实例
  3. 基于FeignClient声明的接口被解析后,如何存储?
  4. 在发起方法调用时,整体的工作流程是什么样的?
  5. OpenFeign是如何集成Ribbon做负载均衡解析?

带着这些疑问,开始去逐项分析OpenFeign的核心源码

OpenFeign注解扫描与解析

思考, 一个被声明了@FeignClient注解的接口,使用@Autowired进行依赖注入,而最终这个接口能够正常被注入实例。

从这个结果来看,可以得到两个结论

  1. @FeignClient声明的接口,在Spring容器启动时,会被解析。
  2. 由于被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目录下,分别创建

  1. HelloService.java
  2. GpImportBeanDefinitionRegistrar.java
  3. EnableGpRegistrar.java
  4. TestMain
  1. 定义一个需要被装载到IOC容器中的类HelloService
public class HelloService {
}
  1. 定义一个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);
}
}
  1. 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain { public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class)); }
}
  1. 通过结果演示可以发现,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容器。
}

综上代码分析,其实实现逻辑很简单。

  1. 创建一个BeanDefinitionBuilder。
  2. 创建一个工厂Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean中
  3. 调用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的构建过程。

  1. 先定义一个接口,这个接口可以类比为我们上面描述的FeignClient.
public interface IHelloService {

    String say();
}
  1. 接着,定义一个工厂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;
}
}
  1. 通过实现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);
}
}
  1. 声明一个注解,用来表示动态bean的注入导入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. 编写测试类,测试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()); }
}
  1. 运行上述的测试方法,可以看到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);
}

两个关键的对象说明:

  1. 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。

  1. 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的上下文信息,比如loggerencoderdecoder等。因此,从这个分析过程中,我们不难猜测到它的原理结构,如下图所示

父子容器隔离的实现方式如下,当调用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方法根据namecontexts容器中获得上下文对象,如果没有,则调用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;}

上述代码,其实也不难理解。

  1. 解析@FeignClient接口声明的方法,根据不同方法绑定不同的处理器。

    1. 默认方法,绑定DefaultMethodHandler
    2. 远程方法,绑定SynchronousMethodHandler
  2. 使用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在实例化过程中所做的事情如下图所示。

总结来说就几个点:

  1. 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
  2. Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
  3. 对目标服务,使用LoadBalance以及Hystrix进行包装
  4. 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
  5. 遍历MethodHandler列表,针对需要远程通信的方法,设置SynchronousMethodHandler处理器,用来实现同步远程调用。
  6. 使用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带你学架构

如果本篇文章对您有帮助,还请帮忙点个关注和赞,您的坚持是我不断创作的动力。欢迎关注「跟着Mic学架构」公众号公众号获取更多技术干货!

Spring Cloud 源码分析之OpenFeign的更多相关文章

  1. Spring Cloud源码分析(四)Zuul:核心过滤器

    通过之前发布的<Spring Cloud构建微服务架构(五)服务网关>一文,相信大家对于Spring Cloud Zuul已经有了一个基础的认识.通过前文的介绍,我们对于Zuul的第一印象 ...

  2. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - WebApplicationContext 容器的初始化

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 日志系统

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

随机推荐

  1. .NET C#教程初级篇 1-1 基本数据类型及其存储方式

    .NET C# 教程初级篇 1-1 基本数据类型及其存储方式 全文目录 (博客园).NET Core Guide (Github).NET Core Guide 本节内容是对于C#基础类型的存储方式以 ...

  2. Java 总结 数据底层原理 【包括 ArrayList、LinkedList、hash table、HashMap、Hashtable、ConcurrentHashMap、hash code、HashSet、LinkedHashMap、LinkedHashSet】

    1.ArrayList (1)底层是由动态数组实现的[使用了List接口]. (2)动态数组是长度不固定,随着数据的增多而变长. (3)如果不指定,默认长度为10,当添加的元素超过当前数组的长度时,会 ...

  3. SYCOJ27猴子选大王

    题目-猴子选大王 (shiyancang.cn) 一.出队顺序Description有M个人,其编号分别为1-M.这M个人按顺序排成一个圈.现在给定一个数N,从第一个人开始依次报数,数到N的人出列,然 ...

  4. 机器学习|线性回归算法详解 (Python 语言描述)

    原文地址 ? 传送门 线性回归 线性回归是一种较为简单,但十分重要的机器学习方法.掌握线性的原理及求解方法,是深入了解线性回归的基本要求.除此之外,线性回归也是监督学习回归部分的基石. 线性回归介绍 ...

  5. 《剑指offer》面试题60. n个骰子的点数

    问题描述 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s.输入n,打印出s的所有可能的值出现的概率. 你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i ...

  6. Rancher Fleet使用教程

    官方文档: https://fleet.rancher.io/ https://github.com/rancher/fleet 博客截止日期为:20201204 当前官网版本为v0.3.0,但在实践 ...

  7. 【小记录】利用cuvid库做视频解码,运行出现"dlopen "libnvcuvid.so" failed!"

    1.查看源码:/Video_Codec_SDK_8.0.14/Samples/common/src/dynlink_nvcuvid.cpp 其中的LOAD_LIBRARY函数的源码如下: 1 #eli ...

  8. 【小记录】arm64下的原子加

    1.代码中使用atomic_add aarch64下面并没有任何关于atomic的头文件 编译出现错误: /Users/ahfu/code/android/android-ndk-r14b/toolc ...

  9. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  10. golang中gomodule讲解

    0. GOMODULES模式 1. GOPATH的缺点 1. 无版本控制概念 2. 无法同步一致第三方版本号 3. 无法指定当前项目引用的第三方版本号 2. go1.11版本之后可以使用GoModul ...