【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(上)

OpenFeign简单介绍

Feign与OpenFeign

Feign对Ribbon和各种http客户端工具类(如OKHttp、HttpClient、HttpURLConnection等)进行了封装,使得开发者无需再手动调用RestTemplate发起http请求,而是以方法调用的方式向远处服务发起请求。

在这里插入图片描述

当使用Feign之后,开发者要做的就是在接口以及接口方法上使用Feign的注解修饰,Feign就会扫描并给这些接口生成一个代理对象,当我们调用接口的方法时,实际上就是调用代理对象,底层就会通过Ribbon进行负载均衡,然后使用http客户端发起http请求。

在这里插入图片描述

而OpenFeign就是在Feign的基础上复用了SpringMVC的注解,更加的方便开发者使用,降低学习门槛。

在这里插入图片描述

OpenFeign使用示例

一共四步:

在这里插入图片描述

  1. 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 在启动类上添加@EnableFeignClients注解以启用Feign客户端功能
@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 定义Feign接口,用于声明要调用的服务方法
@FeignClient(name = "service-provider")
public interface ServiceClient {

    @GetMapping("/api/users/{id}")
    User getUser(@PathVariable("id") Long id);

    @PostMapping("/api/users")
    User createUser(@RequestBody User user);
}
  1. 在需要使用服务调用的地方注入并使用这个Feign接口
@Service
public class UserService {

    private final ServiceClient serviceClient;

    @Autowired
    public UserService(ServiceClient serviceClient) {
        this.serviceClient = serviceClient;
    }

    public User getUserById(Long id) {
        return serviceClient.getUser(id);
    }

    public User createUser(User user) {
        return serviceClient.createUser(user);
    }
}

在这个示例中:

  • @FeignClient 注解用来定义一个Feign客户端,它会代理指定服务的所有接口。
  • 接口中的方法通过HTTP动词和路径来表示对远程服务API的调用。
  • 方法参数可以通过Spring MVC的注解进行映射,如 @PathVariable 和 @RequestBody。

这样,您就可以像调用本地方法一样调用远程服务的方法,而OpenFeign会在后台帮我们处理好所有HTTP请求相关的细节。

OpenFeign原理解析

  1. 使用scanner包扫描器扫描@FeignClient修饰的接口,生成BeanDefinition
  2. 修改BeanDefinition的beanClass属性为FeignClientFactoryBean
  3. FeignClientFactoryBean实现了FactoryBean,getObject()方法进行动态代理生成代理对象
  4. 代理对象内部会调用LoadBalancerFeignClient通过Ribbon负载均衡器执行Feign请求并获取响应

在这里插入图片描述

OpenFeign源码解析

@EnableFeignClients

...
// 导入FeignClientsRegistrar,FeignClientsRegistrar进行包扫描并注册BeanDefinition
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	/**
	 * basePackages()属性的别名
	 */
	String[] value() default {};

	/**
	 * 指定的包扫描路径
	 */
	String[] basePackages() default {};

	...

}

@EnableFeignClients内部会通过@Import导入FeignClientsRegistrar,FeignClientsRegistrar会进行包扫描并注册BeanDefinition。

在这里插入图片描述

FeignClientsRegistrar

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ... {
	...
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		...
	}
}

FeignClientsRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口。当我们通过@Import导入ImportBeanDefinitionRegistrar类型的bean时,Spring会调用ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法。OpenFeign在registerBeanDefinitions()方法进行了包扫描并注册Feign客户端。

在这里插入图片描述

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		...
		// 进行包扫描并注册Feign客户端
		registerFeignClients(metadata, registry);
	}
	...
	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 使用Spring提供的包扫描器ClassPathScanningCandidateComponentProvider进行包扫描
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		...
		for (String basePackage : basePackages) {
			// 进行包扫描,扫描@FeignClient注解修饰的接口
			// 返回扫描加载到的BeanDefinition
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					...
					// 注册到Spring容器
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

OpenFeign在registerBeanDefinitions()中,使用了Spring提供的包扫描器ClassPathScanningCandidateComponentProvider进行包扫描,扫描@FeignClient注解修饰的接口,生成BeanDefinition并返回。

在这里插入图片描述

	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		// 设置BeanDefinition的beanClass属性为FeignClientFactoryBean类型
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		...
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		...
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		// 注册到Spring容器
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

获取到包扫描返回的BeanDefinition之后,设置BeanDefinition的beanClass为FeignClientFactoryBean,最后注册到Spring容器中。

在这里插入图片描述

FeignClientFactoryBean

class FeignClientFactoryBean
		implements FactoryBean<Object>, ... {
	
	@Override
	public Object getObject() throws Exception {
		// 动态代理生成代理对象 ...
	}
	
}

FeignClientFactoryBean实现了Spring的FactoryBean接口,如果一个bean实现了FactoryBean接口,Spring会在实例化bean的时候,调用getObject()方法进行实例化,而不会进行常规的推断构造进行反射实例化。而FactoryBean#getObject()方法配合动态代理是常用的套路,OpenFeign也是如此。

在这里插入图片描述

  @Override
  public <T> T newInstance(Target<T> target) {
    ...
    // JDK动态代理的InvocationHandler,类型是FeignInvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 使用JDK动态代理返回代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    ...
    return proxy;
  }

FeignClientFactoryBean的getObject()方法最终会调用到ReflectiveFeign#newInstance()方法,里面通过JDK动态代理生成代理对象并返回,代理对象对应的InvocationHandler是FeignInvocationHandler。

在这里插入图片描述

FeignInvocationHandler

生成的代理对象就会被Spring处理@Autowired注解时注入到对应接口类型的属性当中。当我们调用该接口的方法时,实际上调用的就是代理对象,然后就会被FeignInvocationHandler拦截处理。

FeignInvocationHandler#invoke():

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      ...
      return dispatch.get(method).invoke(args);
    }

dispatch.get(method).invoke(args)调用进入到SynchronousMethodHandler#invoke():

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    ...
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (...) {...}
    }
  }
  
  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    ...
    try {
      response = client.execute(request, options);
    } catch (IOException e) {...} finally {...}
  }

client.execute(request, options)调用就会进入到LoadBalancerFeignClient的execute()方法,因此FeignInvocationHandler就是调用LoadBalancerFeignClient进行负载均衡以及发送http请求等处理的。

在这里插入图片描述

LoadBalancerFeignClient

LoadBalancerFeignClient里面的处理使用到了RxJava,稍微有点复杂,如果不想看的话,只需要知道LoadBalancerFeignClient其实就是干了以下三件事:

  1. 负载均衡:通过Ribbon的ILoadBalancer的chooseServer()方法选出一个实例
  2. 重写url:根据选出的实例重构URI
  3. 发起http请求:默认使用HttpURLConnection,返回Response对象

在这里插入图片描述

如果还打算继续研究LoadBalancerFeignClient里面的细节,请看下一篇文章,做好烧脑的准备。

下面附上本次源码解析的流程图:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值