Feign 源码分析
- 当我们用FeignClient来坐微服务之间的服务调用时候,我们需要实现如下的一个FeignClient的接口
@FeignClient(name = "zaip-pms-decisioncalc-service",configuration = DecisioncalcConfiguration.class)
public interface StockFeignClient {
@GetMapping("/stock/reduce/{produceId}")
String reduce(@PathVariable("produceId") String produceId);
}
- 如上代码中:
- 我们通过注解@FeignClient可以得到服务名称 zip-pms-decisioncalc-service
- 通过SpringMVC注解@GetMapping可用得到他是发送一个Get请求,路径是/stock/reduce/{produceId}
- 通过解析他的参数,我们就能得到最终要请求的一个地址信息如下:http://zaip-pms-decisioncalc-service/stock/reduce/{produceId}
- 那FeignClient的作用就是将以上这个interface的定义转化成如上的请求连接,并且替换成IP+port的请求方式,并且完成http请求,那么我们从Feign的源码入手来分析他是如何做到这一点的
找入口
- 在使用FeignClient的时候,必不可少的注解是@EnableFeignClients,这个注解作用是启动我们的Feign,如下代码
@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 {};
}
- 如上源码中,他利用了@Import(FeignClientsRegistrar.class) 来注入FeignClientsRegistrar
- @Import的作用参考如下链接:https://blog.csdn.net/liaojiamin0102/article/details/129443392
- @Import的作用是导入三种类似,分别如下:
- {@link Configuration @Configuration}
- {@link ImportSelector} 他必须是SelectImpot 的实现类,返回的数组,批量加载类,SpringBoot自动装配的原理
- {@link ImportBeanDefinitionRegistrar},他必须是ImportBeanDefinitionRegistrar 的实现类,所以他需要实现ImportBeanDefinitionRegistrar 中的方法。registerBeanDefinitions(注册bean)
Feign 注册代理类Bean流程
- 通过registerBeanDefinitions 方法为入口,我们找到如下代码:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
- 关键入口是。registerFeignClients,他是注册Feign代理类Bean的入口,FeignClient修饰的都是接口,目的是将接口转换成相应的BeanDefinition并且注册到Spring容器中
- 流程如下:
- 首先通过getScanner()来获取一个扫描器,他会遍历你配置的类路径(没有配置默认用当前类路径)
- 通过scanner的过滤器来实现对应目录下每一个类的扫描,代码如下:
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
- 如上代码:
- scanner扫描器目的是将带上了@FeignClient注解的所有类筛选出来得到一个数组LinkedHashSet candidateComponents
- 接着利用得到的元数据信息进行注册:registerFeignClient(registry, annotationMetadata, attributes);
- 得到元数据后,进行Bean注册但是我们FeignClient修饰的都是Interface,接口无法直接实例化因此无法直接注入到Spring容器中,因此我们需要创建一个代理对象来替代当前的Interface,并且将代理对象注入到Spring容器中
代理对象创建
第一步获取创建代理类需要的上下文信息(可以认为是Bean属性)
- 要创建一个Bean对象注入到Spring容器中,一般有两种方式:
- 第一种通过BeanFactory,通过他创建SpringBean对象需要严格遵循Bean创建的生命周期流程,太复杂
- 第二种通过FactoryBean,他是一个接口,可以通过实现接口中的getObject方法来完成自定义对象创建
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
.......
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
.......
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
.......
return factoryBean.getObject();
});
.......
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
- FeignClien他使用的是第二种,在registerFeignClient方法中,初始化了一个FeignClientFactoryBean,在自定义getObject方法中,他给每一个FeignClient接口生成个了一个Spring上下文信息AnnotationConfigApplicationContext,
- 创建AnnotationConfigApplicationContext所需要的组建是Feign组建的Spring上下文中获取此处是FeignClientsConfiguration他定义了Feign对象创建必要的信息
- 创建之后的对象放入Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap()中,key就是服务名:zaip-pms-decisioncalc-service value就是创建的AnnotationConfigApplicationContext Spring上下文信息。
第二部通过上下文信息创建代理类
- getObject方法中,通过loadBanance方法入口创建代理类
......
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
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;
}
- 通过newInstance 方法中factory.create创建了一个FeignInvocationHandler,他是JDK动态代理中InvocationHandler 的实现类
- 利用JDK的动态代理技术生成一个代理类proxxy,这个代理类就是我们需要注入到Spring容器中
- 之后返回到最初定义FactorBean的地方,通过BeanDefinitionReaderUtils.registerBeanDefinition,将得到的代理类元数据信息注入到Spring容器中
- 至此,FeignClient 修饰的接口,对象创建注入的过程就结束了
FeignClient代理类调用
- 在调用Feign请求的时候,其实就是调用JDK动态代理生成的代理类proxy,那么他一定会调用代理类中的invoke方法,也就是FeignInvocationHandler.invoke方法
- 最终会调用SynchronousMethodHandler 类的invoke方法,在方法中执行最终的HTTP调用
- 第一步,executeAndDecode(template, options);
- 第二步,client.execute(request, options);
- 第三步,lbClient(clientName) 通过Ribbon选择一个服务
- 第四步,AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());通过Ribbon重构URI,将之前服务名修改为IP+port的形式,如下图
- 第五步,发起Http请求:Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
- 第六步:FeignLoadBalancer.execute 利用HttpUrlConnection发起http请求
public Object invoke(Object[] argv) throws Throwable {
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;
}
}
}