原理-源码
让我们看着源~码~ 按顺序走~趟流~程~
分为两大部分:启动原理、调用流程
Feign 的Java 代码才 3w 多行,放眼现在热门的开源项目, Dubbo、Naocs、Skywalking 中 Java 代码都要 30w 行起步。
重要通知!看源码,
就上新东方一定要找对地方!!!
在一个SpringCloud项目中,我们引入了spring-cloud-openfeign依赖,而该依赖又引入了openfeign-core的依赖;
他们分别对应的github代码仓库分别是spring-cloud-openfeign和OpenFeign/feign
spring-cloud-openfeign是openFeign和SpringCloud的结合,我们的@EnableFeignClients注解、服务启动时的初始化工作就在此项目中
openfeign-core是OpenFeign/feign仓库的core模块,该仓库是feign的“娘家”,单独作为远程调用组件而存在的feign,没有“嫁入Spring Cloud豪门”(未被整合进Spring Cloud成套解决方案的单独组件,若单独使用,则启动流程略有不同)
- 比如不会随启动自动注册Spring Bean,没有我们刚学的一些注解,
- 仓库内还包含了不需要学习的测试模块、备选的http库实现等。
比如启动流程,在注册、代理Bean的过程中,会先从SpringCloud OpenFeign的源码看起,而其中引入的Feign包下的源码,则需要转到OpenFeign/feign仓库;
本文为了方便大家学习,会将他们整合到一起,需要跨仓库的地方会提一嘴,做到梳理流程无痛衔接。毕竟我们学习目的是流程原理,而不是为了学某一具体仓库。
依赖
总项目
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
core模块
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<exclusions>
<!-- Vulnerable in 3.8.0-->
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-java11</artifactId>
<optional>true</optional>
</dependency>
辞典
contextId: FeignClient的容器id,通过该id从Spring容器中获取Bean;
代码风格
Builder\Factory都定义在自己内部。看到XXX.Builder,就知道是用来创建XXX的。
几乎所有获取对象的方法,都是用target()方法,内部build().newInstance()
OpenFeign,启动!(原理)
服务启动阶段,我们需要读取配置、注册Bean;
需要注册的Bean组件有:
启动时factories
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
- 这个和注解扫描谁先谁后?
- 注解是怎么扫描的?
入口类注解@EnableFeignClients
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
@Import 常用于动态注册Spring Bean
FeignClientsRegistrar.class实现ResourceLoaderAware、EnvironmentAware接口, 重写Set方法为 FeignClientsRegistrar 中两个属性 resourceLoader、environment 赋值,
- can can spring
ImportBeanDefinitionRegistrar 负责动态注入 IOC Bean,分别注入 Feign 配置类、FeignClient Bean
FeignClientsRegistrar源码
// 资源加载器,可以加载 classpath 下的所有文件
private ResourceLoader resourceLoader;
// 上下文,可通过该环境获取当前应用配置属性等
private Environment environment;
//..
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册 @EnableFeignClients 提供的自定义配置类中的相关 Bean 实例
registerDefaultConfiguration(metadata,registry);
// 扫描 packge,注册被 @FeignClient 修饰的接口类为 IOC Bean
registerFeignClients(metadata, registry);
}
- 为什么有了factory还要有factoryBean?
注册 FeignClient 接口
registerFeignClients方法处理带有 @FeignClient 的接口
1.扫描 @EnableFeignClients 注解,如果有在clients属性上配置client,则加载指定接口;若没有配置,则用 scanner在bashPackage下扫描出被@FeignClient 修饰的接口
2.获取 @FeignClient 上的属性,根据 configuration 属性去创建接口级的 FeignClientSpecification 配置类 IOC Bean
3.将 @FeignClient 注解上的属性设置到 FeignClientFactoryBean 对象上,并注册 IOC Bean
@FeignClient 修饰的接口使用了 Spring 的代理工厂生成代理类,所以这里会把修饰了 @FeignClient 接口的 BeanDefinition 设置为 FeignClientFactoryBean 类型,而 FeignClientFactoryBean 继承自 FactoryBean
根据Spring加载Bean的过程,当我们用@FeignClient 修饰interface时,注册到 IOC 容器中 Bean 类型其实是其工厂 FeignClientFactoryBean;当我们要获取这个bean时,再调用工厂的getObjtct()生产出bean;
之前我们手写工厂模式的案例的时候,在创建对象时经常是亲自调用Factory的创建方法;
但在Spring中,(作为中间层的容器层?)Spring统一包揽了Bean对象的创建和管理。我们可以只要无脑getbean,而具体细节,如是先用工厂创建还是直接返回之前的Bean ,直接由中间的Spring管理了,我们只要无脑get。甚至在注解方式下,Get都省了,直接@Autowired。
- 这里既有代理(?)又有工厂的思想。代理下,我们只管找中间层要bean这一“产品”,别管这东西哪儿来的,甚至别管有没有生产出来,这让代理想办法;
- 而代理如果缓存里有Bean就直接返回,没有的话他自己再找工厂要。这种具体的小事不要烦调用它的程序猿。
- 突然有种当领导的感觉,哈哈。大决战的林总。
- 如果说工厂模式解决了创建对象不需要知道具体对象细节的问题,那么工厂+代理就一条龙解决了不用知道工厂细节、管理细节的问题,哈哈。
在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。/对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,而是会返回执行了工厂 Bean 中 FactoryBean#getObject 逻辑的实例
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
// if @EnableFeignClients参数中有name属性的情况,此处暂且省略
else {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = this.getBasePackages(metadata);
Iterator var8 = basePackages.iterator();
while(var8.hasNext()) {
String basePackage = (String)var8.next();
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
Iterator var13 = candidateComponents.iterator();
while(var13.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var13.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
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 = this.getClientName(attributes);
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, (ClassLoader)null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null;
String contextId = this.getContextId(beanFactory, attributes);
String name = this.getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(this.isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(this.getUrl(beanFactory, attributes));
factoryBean.setPath(this.getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class)fallback : ClassUtils.resolveClassName(fallback.toString(), (ClassLoader)null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class)fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), (ClassLoader)null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(2);
definition.setLazyInit(true);
this.validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute("factoryBeanObjectType", className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = this.getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[]{contextId + "FeignClient"};
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
this.registerOptionsBeanDefinition(registry, contextId);
}
}
相关类
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
return isCandidate;
}
};
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet();
String[] var4 = (String[])((String[])attributes.get("value"));
int var5 = var4.length;
int var6;
String pkg;
for(var6 = 0; var6 < var5; ++var6) {
pkg = var4[var6];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
var4 = (String[])((String[])attributes.get("basePackages"));
var5 = var4.length;
for(var6 = 0; var6 < var5; ++var6) {
pkg = var4[var6];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
Class[] var8 = (Class[])((Class[])attributes.get("basePackageClasses"));
var5 = var8.length;
for(var6 = 0; var6 < var5; ++var6) {
Class<?> clazz = var8[var6];
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
依据 Spring InitializingBean 接口:FeignClientFactoryBean在类初始化时执行一段逻辑;
依据 Spring FactoryBean 接口:如果它被别的类 @Autowired 进行注入,返回的不是它本身,而是 FactoryBean#getObject
返回的类,
依据 Spring ApplicationContextAware 接口:它能够获取 Spring 上下文对象,赋值到对象的局部变量里。
- Resource怎么样?
初始化逻辑没有特别的操作,只是使用断言工具类判断两个字段不为空。
@Override
public void afterPropertiesSet() {
Assert.hasText(contextId, "Context id must be set");
Assert.hasText(name, "Name must be set");
}
关键看 FactoryBean#getObject 方法
@Override
public Object getObject() throws Exception {
return getTarget();
}
- Target是什么?
getTarget() 源码挺长的,这里分段展示:
这里提出一个疑问:FeignContext 什么时候、在哪里被注入到 Spring 容器里的?
使用了 SpringBoot 自动装配的功能,FeignContext 就是在 FeignAutoConfiguration 中被成功创建
初始化父子容器
这里涉及到 Spring 父子容器的概念,默认子容器 Map 为空,获取不到服务名对应 Context 则新建
从下图中看到,注册了一个 FeignClientsConfiguration 类型的 Bean,我们上述方法 feign 中的获取的编码、解码器等组件都是从此类中获取默认
默认注册如下,FeignClientsConfiguration 是由创建 FeignContext 调用父类 Super 构造方法传入的
关于父子类容器对应关系,以及提供 @FeignClient 服务对应子容器的关系(每一个服务对应一个子容器实例)
回到 getInstance 方法,子容器此时已加载对应 Bean,直接通过 getBean 获取 FeignLoggerFactory
如法炮制,Feign.Builder、Encoder、Decoder、Contract 都可以通过子容器获取对应 Bean
configureFeign 方法主要进行一些配置赋值,比如超时、重试、404 配置等,就不再细说赋值代码了
到这里有必要总结一下创建 Spring 代理工厂的前半场代码
- 注入@FeignClient 服务时,调用 FactoryBean#getObject 返回的代理对象
- 通过 IOC 容器获取 FeignContext 上下文
- 创建 Feign.Builder 对象时会创建 Feign 服务对应的子容器
- 从子容器中获取日志工厂、编码器、解码器等 Bean
- 为 Feign.Builder 设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置
用applicationContext.getBean()获取FeignContext,然后用feign(context)获取Builder;
然后先以FeignContext为参数创建Client对象,再用Builder构建Client。
又一个Builder案例
动态代理生成Bean
继续看FeignClientFactory#getTarget方法:
此时的Targeter是HystrixTargeter的对象,Client则是LoadBalanceFeignClient
新版不是Hystrix了,而是自己的FeignCircuitBreakerTargeter.java
<T> T getTarget() {
FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
: applicationContext.getBean(FeignClientFactory.class);
Feign.Builder builder = feign(feignClientFactory);
//if分支:@注解上没有url的话,则使用负载均衡的Client
if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
//...log
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(feignClientFactory, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
applyBuildCustomizers(feignClientFactory, builder);
Targeter targeter = get(feignClientFactory, Targeter.class);
return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
}
targeter: 一个接口,用target方法返回一个对象(此处为FeignClient的bean);
负载均衡的targeter直接从FeignClientFactory context根据HardCodedTarget类获取。需要引入spring-cloud-starter-loadbalancer模块,可能是在那里放入上下文的吧。
最终的targeter.target()返回的是Feign.Builder的target方法返回的对象;
而Feign.Builder又是feign(feignClientFactory)返回的,是用get(FeignClientFactory context, Feign.Builder.class)从上下文获取的
- import feign.Target.HardCodedTarget;哪里的代码?没见feign,是依赖里的么
- Feign.Builder这个包没找到,倒是FeignClientBuilder.java里有个内部类Builder,但应该不是这个
- 是feign仓库里的。但是FeignClientBuilder已经是个builder了还要内部Builder?
这下不得不看get方法了:
protected <T> T get(FeignClientFactory context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("也妹(没)找着这个bean呐!");
}
return instance;
}
看源码怕太枯燥了,报错信息我就整活翻译了下hhh
- contextid那儿来的?
factory为啥是context?
FeignClientFactory的getInstance方法:
public <T> T getInstance(String contextName, String beanName, Class<T> type) {
return getContext(contextName).getBean(beanName, type);
}
getContext可能是父类NamedContextFactory<FeignClientSpecification>
的方法,这里can can spring。
终于是返回了Builder了。
Client: Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端
代码中的 Client、Targeter 在自动装配时注册,配合上文中的父子容器理论,这两个 Bean 在父容器中存在
为了使用负载均衡,我们常在 @FeignClient 注解是使用 服务name 而不是 url。因为如源码所示,不传入url才会执行负载均衡策略的分支, return一个loadBalance()返回的targeter.target()。
- 注入这个?那Client哪里去了?
Feign依赖的spring-cloud-loadbalancer,
获取到Builder和Targeter之后,我们将Builder传给Targeter的getTarget(),获取我们想要的FeignClient的代理类
- 怎么知道是FeignCircuitBreakerTargeter? 自动装备时注册的?
- 那怎么样又让他不是呢?
查看FeignCircuitBreakerTargeter的 target()方法
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {
return feign.target(target);
}
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
return builder(name, builder).target(target);
}
判断传入的Builder是FeignCircuitBreaker.Builder对象,调用其target(target)方法,该方法返回build(null).newInstance(target)。
@Override
public <T> T target(Target<T> target) {
return build(null).newInstance(target);
}
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,
circuitBreakerGroupEnabled, circuitBreakerNameResolver));
return super.build();
}
其父类是Feign.Builder。这里是先设置invocationHandlerFactory,再调用父类的build方法:
feign/core/src/main/java/feign/Feign.java
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
super.enrich();
final ResponseHandler responseHandler =
new ResponseHandler(logLevel, logger, decoder, errorDecoder,
dismiss404, closeAfterDecode, responseInterceptor);
MethodHandler.Factory<Object> methodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
responseHandler, logger, logLevel, propagationPolicy,
new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
options);
return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,
() -> null);
}
}
好家伙,不但自己内部定义factory接口,build也是定义到自己内部,自己建造自己了属于是
Feign.bulild()创建反射类 ReflectiveFeign的对象,该类设置编码、解码、重试等属性;
- Feign对象到底是干什么的?返回他干嘛
- 怎么设置的? 和Dubbo对比如何?
ReflectiveFeign的 newInstance(target) 方法对 @FeignClient 修饰的接口中 SpringMvc 等配置进行解析转换,对接口类中的方法进行归类,生成动态代理类
newInstance()按照行为大致划分,共做了四件事
- 处理 @FeignClient 注解(SpringMvc 注解等)封装为 MethodHandler 包装类
- 遍历接口中所有方法,过滤 Object 方法,并将默认方法以及 FeignClient 方法分类 为啥要过滤?
- 创建动态代理对应的 InvocationHandler 并创建 Proxy 实例
- 接口内 default 方法 绑定动态代理类 为啥是他们
feign/core/src/main/java/feign/ReflectiveFeign.java
/**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
public <T> T newInstance(Target<T> target) {
return newInstance(target, defaultContextSupplier.newContext());
}
@SuppressWarnings("unchecked")
public <T> T newInstance(Target<T> target, C requestContext) {
TargetSpecificationVerifier.verify(target);
Map<Method, MethodHandler> methodToHandler =
targetToHandlersByName.apply(target, requestContext);
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (MethodHandler methodHandler : methodToHandler.values()) {
if (methodHandler instanceof DefaultMethodHandler) {
((DefaultMethodHandler) methodHandler).bindTo(proxy);
}
}
使用参数传来的invocationFactory创建InvocationHandler。在该案例中,传来的Factory是cloud中的new FeignCircuitBreakerInvocationHandler。
看来cloud OpenFeign和feign的不同,这里是一处
- InvocationHandler和methodhandler什么区别?说起来我连这个类干什么的都不知道。proxy怎么创建的,这种handler反射怎么实现的,也有必要严查!!
- 从上文代码来看DefaultMethodHandler才进行最后的绑定,那要是不是Default的呢?方法怎么代理?
MethodHandler 将方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储
- 似乎是因为Proxy.newInstance()方法需要handler
- 返回的是泛型T类型
- mvc的什么配置?
- 大概知道为什么自己看不懂了,,一堆target,给target()传入target参数来返回target,,这Tm谁写的代码
可以看出 Feign 创建动态代理类的方式和 Mybatis Mapper 处理方式是一致的,因为两者都没有实现类
- mapper咋创建的来着,细节也一样吗?
创建MethodHandler
feign/core/src/main/java/feign/ReflectiveFeign.java自己内部一通瞎jb调用后:
private MethodHandler createMethodHandler(final Target<?> target,
final MethodMetadata md,
final C requestContext) {
if (md.isIgnored()) {
return args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
};
}
return factory.create(target, md, requestContext);
}
}
没错这个MethodHandler的Factory还是定义在他自己内部的。至于实现,是创建反射Feign代理对象的时候new出来的SynchronousMethodHandler,刚才已经看到源码了
我以为这个叫createXX的方法要自己亲自创建methon了,没想到还是调用内部工厂类
接口自己里面定义自己的工厂接口,实现的时候也是实现类+内部实现接口,哈哈。内部接口语法上要求内部实现吗?
原理总结:
@EnableFeignClient扫描带有@FeignClient注解的接口,生成动态代理类,并为接口内的方法绑定methodHandler;(注册成Bean?)我们用@Autowired注入的bean其实就是自动生成的代理类,按Spring的正常流程,调用FactoryBean的getObject()方法生成。
- 为什么不直接代理或者重写,而是用Handler?类也用Handler,方法也handler
- 什么时候扫描的?怎么扫描?
- 什么时候具体生成这个类?
也就是说在我们调用 @FeignClient接口时,会被 FeignInvocationHandler#invoke 拦截,并在动态代理方法中进行远程调用
- 拦截?应当说注入的是代理类吧。还是说被Handler拦截了?
调用流程
-
具体的还是要先看看Ribbon
-
methodHandler到底是feign中的还是cloud中的那个?
- 应该是feign中的。cloud的invocationHandler不是处理方法的。
- 也许要看看spring AOP动态代理原理是否相同。因为handler反向代理的方式没自己的反射中见过。
-
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
FeignCircuitBreakerInvocationHandler.java
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
if ("equals".equals(method.getName())) {
//...对equals\hashcode等Object类的方法,不用代理
}
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
: factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
if (this.nullableFallbackFactory != null) {
Function<Throwable, Object> fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception exception) {
unwrapAndRethrow(exception);
}
return null;
};
return circuitBreaker.run(supplier, fallbackFunction);
}
return circuitBreaker.run(supplier);
}
- 接口注解信息封装为 HTTP Request
- 通过 Ribbon 获取服务列表,并对服务列表进行负载均衡调用(服务名转换为 ip+port)
- 请求调用后,将返回的数据封装为 HTTP Response,继而转换为接口中的返回类型
feign/
@Override
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;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
// ...log
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// ... log and throw
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
return responseHandler.handleResponse(
metadata.configKey(), response, metadata.returnType(), elapsedTime);
}
- methodHandler是私有构造方法,是单例模式吗?
封装Request
如上文MethodHandler的invoke()所示:
先创建requestTemplate:调用传入的RequestTemplate.Factory这一内部接口的create(Object[] args)方法创建。
还是第一次见到把工厂类接口定义为自己内部接口的工厂模式实现,可能是写着方便吧。
create()方法调用resolve()方法解析参数、返回template实例
RequestTemplate.Factory接口在RequestTemplateFactoryResolver.java
文件中有静态实现类BuildTemplateByResolvingArgs,该类又有两个静态子类,根据参数情况分别调用这三个类的resolve方法。
- 回头研究下springboot的restTemplate怎么做的
将RequestTemplate对象转换成Request对象:在template上aplly拦截器,然后target.applly()即可。
Client.execute()执行请求
- client实例从哪里来的?
- 没看到SynchronousMethodHandler什么时候初始化的,所以不知道Client,,甚至构造方法也是私有的
- 看到了,,是Feign.java创建反射Feign的时候new出的Factory,在反射Feign里,
- Feign. builder定义的时候有个private Client client = new Client.Default(null, null);不知道有其他初始化没有
- 好吧早在FeignClientFactory#getTarget里设置了client,,
直接从上下文getContext(name).getBeansOfType(type);了
- 没看到SynchronousMethodHandler什么时候初始化的,所以不知道Client,,甚至构造方法也是私有的
DefaultLoadBalancerClientConfiguration中用了FeignBlockingLoadBalancerClient;
- 你怎么知道是这个配置类的算话?
- 怎么阻塞的?
FeignBlockingLoadBalancerClient:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
if (instance == null) {
String message = "Load balancer does not contain an instance for the service " + serviceId;
//...服务不可用,自行封装response返回
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl, instance);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors);
}
选择服务
从loadBalancerClient.choose(serviceId, lbRequest);开始,属于spring-cloud-loadbalancer模块
choose()方法是LoadBalancer接口的父接口ServiceInstanceChooser的方法(好家伙接口之间的extends,,不过也是,你总不能implments)
好吧,下一阶段目标:看看自动装配。
话说为什么build模式都不喜欢用setXXX方式命名,,有个http库,,还XXXBuilder.create(),,,谁知道你create()的是目标类还是Builder,,,
执行远端调用逻辑中使用到了 Rxjava (响应式编程),可以看到通过底层获取 server 后将服务名称转变为 ip+port 的方式
这种响应式编程的方式在 SpringCloud 中很常见,Hystrix 源码底层也有使用
网络调用默认使用 HttpURLConnection
调用远端服务后,再将返回值解析正常返回,到这里一个完整的 Feign 调用链就结束了。
补充
启动时,使用了建造者模式
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
feign的InvacationHandler源码真是让人难绷
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
interface Factory<C> {
MethodHandler create(Target<?> target,
MethodMetadata md,
C requestContext);
}
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
一个接口内部定义一个接口,再在内部接口内定义自己的工厂
底下有个内部类直接命名为Default,跟关键字default只差一个大小写
参考资料:
https://zhuanlan.zhihu.com/p/346273428