【深入理解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使用示例
一共四步:
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在启动类上添加@EnableFeignClients注解以启用Feign客户端功能
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 定义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);
}
- 在需要使用服务调用的地方注入并使用这个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原理解析
- 使用scanner包扫描器扫描@FeignClient修饰的接口,生成BeanDefinition
- 修改BeanDefinition的beanClass属性为FeignClientFactoryBean
- FeignClientFactoryBean实现了FactoryBean,getObject()方法进行动态代理生成代理对象
- 代理对象内部会调用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其实就是干了以下三件事:
- 负载均衡:通过Ribbon的ILoadBalancer的chooseServer()方法选出一个实例
- 重写url:根据选出的实例重构URI
- 发起http请求:默认使用HttpURLConnection,返回Response对象
如果还打算继续研究LoadBalancerFeignClient里面的细节,请看下一篇文章,做好烧脑的准备。
下面附上本次源码解析的流程图: