一、SpringBoot中使用 openFeign
在SpringBoot项目中使用openFeign是很简单的,只需要四步
1-1、导入pom文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
1-2、在启动类加入开启feign注解
@EnableFeignClients
1-3、在具体的接口上加feign注解标签
@FeignClient(value = "backend", url = "${marketCloudBackend.url}", configuration = BackendFeignConfiguration.class)
public interface MarketCloudBackendFeign {
@PostMapping(value = "/visitor/aiCallDisVisitor", consumes = "application/json", produces = "application/json")
Object distributePublicCustomer(@RequestBody T params);
}
1-4、在使用的地方注入
@Resource
private MarketCloudBackendFeign marketCloudBackendFeign;
二、openFeign的工作原理
我们知道openFeign是一个rpc远程调用框架,目的是使得我们调用接口和调用本地方法一样简单,本质上还是需要http请求。
openFeign是通过jdk代理来实现这个操作的,下面我们将通过源码一步步探寻它的运作原理。
我们的目的很简单,因为我们在使用的时候是直接进行注入的,我们只需要知道每一个接口是如何被注入到Spring容器的,找到代理所做的事情即可。
2-1、@EnableFeignClients
一切的都是从@EnableFeignClients
注解开始:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
除去@Retention、@Target、@Documented这几个通用注解外,我们看到还有一个@Import
并且引入了 FeignClientsRegistrar.class
2-2、FeignClientsRegistrar
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}
FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar
并重写了里面的方法registerBeanDefinitions
,会自动调用这个方法进行初始化。
FeignClientsRegistrar里面重写registerBeanDefinitions的逻辑如下
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
2-3、registerFeignClients
这个方法主要是去获取使用了@FeignClient
的接口,找到之后进行注入。
这里有两种方式,如果你使用了@EnableFeignClients 的 clients那它就直接去获取对应的class,不通过类路径自动扫描
@EnableFeignClients(clients = {SchedualServiceHi.class, SchedualServiceHi2.class})
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取@EnableFeignClients注解里面的参数
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
Object basePackages;
// 如果使用了clients 就根据class加载
if (clients != null && clients.length != 0) {
((Set)basePackages).add(ClassUtils.getPackageName(clazz));
} else {
// 否则使用类路径加载
basePackages = this.getBasePackages(metadata);
}
// 迭代全部的接口
Iterator var17 = ((Set)basePackages).iterator();
while(var17.hasNext()) {
String basePackage = (String)var17.next();
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
Iterator var21 = candidateComponents.iterator();
while(var21.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var21.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 进行下一步的注册
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
2-4、registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// ....
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
// ...
可以看到registerFeignClient方法里面生成了一个BeanDefinitionBuilder,而入参是FeignClientFactoryBean
2-5、FeignClientFactoryBean
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {}
可以看到它是实现了FactoryBean
里面有一个getObject
是获取bean实例的,FeignClientFactoryBean 重写了这个 getObject
public Object getObject() throws Exception {
return this.getTarget();
}
<T> T getTarget() {
FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
Builder builder = this.feign(context);
// url 为 null 、""、" " StringUtils.hasText 就返回false
if (!StringUtils.hasText(this.url)) {
// ......
} else {
// ......
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
}
}
Targeter 是一个接口,它有两个实现类,我们来看默认的实现类
注:这个FactoryBean接口,可以理解成是Spring的一个钩子,它的getObject方法,是在初始化bean的时候会去调用。(暂时大家先这么理解后面再写一篇文章解释,里面也很复杂)
2-6、DefaultTargeter
class DefaultTargeter implements Targeter {
DefaultTargeter() {
}
public <T> T target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
return feign.target(target);
}
}
target 最终调用的是 Feign.class
方法,如下,可以看到最终是调用ReflectiveFeign.newInstance
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
public Feign build() {
Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}
2-7、ReflectiveFeign.newInstance
在newInstance
方法里面有一个JDK代理,所以openfeign其实是基于JDK代理来实现的
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
JDK代理是基于接口代理的,当然最主要的还是我们的InvocationHandler
,它代表了我们代理对象最终指向的内容,所以下面我们就要来看看这个handler是如何产生的。
2-8、this.targetToHandlersByName.apply(target);
第一行代码,就是获取当前对象的方法
this.contract.parseAndValidatateMetadata(key.type());
最后返回 类名#方法名
为key,MethodHandler
为value的map
存入map的时候调用的方法是
this.factory.create(key, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder))
点进去这个 create
方法,我们看到是new了一个对象,这个类是实现了MethodHandler
return new SynchronousMethodHandler(target, this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, this.decode404, this.closeAfterDecode, this.propagationPolicy);
记住这个 SynchronousMethodHandler
后面还会看到
2-9、this.factory.create(target, methodToHandler);
我们再返回newInstance
方法,可以看到最终是调用了一个 this.factory.create
来创建 InvocationHandler
InvocationHandlerFactory
的 create
里面自己就对这个接口进行实现 return new FeignInvocationHandler(target, dispatch);
2-10、FeignInvocationHandler
FeignInvocationHandler 实现了 InvocationHandler 接口,重写了里面的 invoke
方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
可以看到它最终是调用MethodHandler的 invoke方法。
2-11、SynchronousMethodHandler
上面我们说到最终的MethodHandler其实是SynchronousMethodHandler
,那么我们只需要看这个里面的invoke方法就好了
这个里面其实就是显现了http的调用
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(template);
} catch (RetryableException var8) {
// 为了方面看,我去除了异常处理部分
}
}
}
原文始发于微信公众号(小道仙97):源码学习之【OpenFeign工作原理】
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/41313.html