原理-源码
让我们看着源~码~ 按顺序走~趟流~程~
分为两大部分:启动原理、调用流程
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 容器里的?![请添加图片描述](https://img-blog.csdnimg.cn/37ff2926b178428c8e0af5873ed66bbe.bmp)
使用了 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