OpenFeign的使用步骤
在我们使用 OpenFeign 的时候只需要添加两个注解即可正常使用(添加依赖、在配置文件yml指定了注册中心后)
- 一个是主启动类上的
@EnableFeignClients - 另一个是接口类的
@FeignClient
主启动类
/**
* 主启动类
*/
@SpringBootApplication
@EnableEurekaClient // 这里的注册中心采用的是 Eureka
@EnableFeignClients
public class OpenFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderByOpenFeignMain80.class,args);
}
}
FeignClient 接口
@Service
@FeignClient(value = "PROVIDER-8001")
public interface OrderFeignClient {
@GetMapping("payment/hello")
String hello();
/**
* 流程 : provider注册进eureka,eureka中有controller接口的映射信息
* 此时,消费者可以从eureka中获取相应映射,并通过反射生成代理类实现接口
*
* Feign使用JDK动态代理技术时,需要提前将接口(例如OrderService)带 @RequestMapping/@GetMapping.../... 之类的方法解析出来
*/
}
使用高版本 OpenFeign 的踩坑点
做负载均衡时,低版本的 OpenFeign 集成了 Ribbon 的负载均衡,但高版本的 OpenFeign 把 LoadBalancer 剔除掉了
因此想要做到集群负载均衡的话,要额外添加 LoadBalancer 的依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- LB负载均衡依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
标注了 @FeignClient 的类是怎么自动注入到 Spring 容器中的?
首先我们来看看主启动类上的注解:@EnableFeignClients
@EnableFeignClients 的定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//注解的属性省略
}
我们看到了这个注解 Import 了一个注册类:FeignClientsRegistrar
继续点进这个类看看是啥情况
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}
我们看见了,这个类,实现了 ImportBeanDefinitionRegistrar 接口,我们知道在处理 @Configuration 类时可以通过 @Import 注册其他Spring Bean定义的能力
在上面提到一个疑问:标注了 @FeignClient 的类是怎么自动注入到 Spring 容器中的?是怎么扫描到标注了 @FeignClient 的类的?
我们看看这个FeignClientsRegistrar类的方法,重点是 registerFeignClients() 方法!!!
// 截取了部分代码
// 注:这个源码的版本是 3.1.1,不同的源码可能稍有出入
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取 @EnableFeignClients 上的相关属性并用这些属性做一些基本配置Bean的注册
registerDefaultConfiguration(metadata, registry);
// 注册Bean
registerFeignClients(metadata, registry);
}
// 主要方法
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 获取包路径下的扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 将所有 @FeignClient 的接口的 BeanDefinition 拿到
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface(源码的注释)
// 看,源码也说了,这里必须要使用接口,@FeignClient 注解必须在接口上面
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 根据这些属性和接口来注册 FeignClient Bean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
// 注册 FeignClient Bean
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 使用FactoryBean,将Bean的具体生成过程收拢到FeignClientFactoryBean之中
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("type", className);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
// 将这个使用了 @FeignClient 的接口的工厂Bean的 BeanDefinition 注册到Spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
所以在 FeignClientRegistrar 类中需要做的就是扫描某些路径的接口类,识别对应的 @FeignClient ,给这些接口类创建代理对象。而为了把这些代理对象注入到Spring 容器中,所以还得借助原始的 Spring 中的 FactoryBean 的能力。
(扫描的路径:配置Spring扫描路径、@EnableFeignClients中配置的路径)
再往后 FeignClientFactoryBean 就是拿到业务接口、然后去构造代理类了。
FeignClientFactoryBean 的底层是使用了 Feign 来实现代理对象的