1.Feign的上下文还有反射代理机制就在于Feign的上下文解析的过程,所以这块也是Feign的核心模块
2.所有feign的动态代理对象都是通过newInstance创建的
1.首先在构建上下文的入口是在FeignClientFactoryBean的getObject方法
2.首先第一步就是构建了FeignContext类
public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
3.然后我们进入到feign这个方法中看下:
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
可以看到首先获取两个日志对象一个FeignLoggerFactory,一个Logger,然后就是把这两个日志对象赋值给feign这个对象,我们看的Builder其实类似于方法糖,让赋值更加方便而已,类似于lombok中的@builder注解一样。
除了赋值日志对象以外,还有加密解密的一个赋值操作,这个是url解析过程中也是非常常见的。然后其他的我们暂时不关注,只要知道这里是初始化了feign对象,一个空的对象,构造器。
4.然后我们继续在主方法往下走
在上一个帖子上我们注意到url是空的,那么在这里就需要使用到了,从源码中看到如果url是空的,那么就使用服务名称作为ip了,然后如果不是http开头,这里就会给一个http的前缀上去,因为feign也是基于eureka的http接口的。
我们在看下下一个方法啊 cleanPaht();
private String cleanPath() {
String path = this.path.trim();
if (StringUtils.hasLength(path)) {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
这里其实还是拼装url的操作,在@FeignClient注解中有一个path的属性,这个属性干嘛用的呢,如图:
而这个path我们最后是不需要/的,所以这里其实就是格式化一下而已,正确的拼接好url的操作。
5.主方法继续
这里就是构造动态代理对象的地方了。
我们点进去看:
可以看到首先调用了getOptional这个方法,然后使用了
context.getInstance(this.name, type);
传入了一个调用的服务名称就是 eureka-client这个服务名称,还有一个 feign.Client的类。
其实这个方法就是从上下文中拿到调用了这个服务的接口作为一个client。
然后把client放到Feign对象中,再构建一个Targeter类
6.然后我们进入targeter.target这个方法看看这个最核心的地方
这块逻辑做了一个判断,看这个feign是不是hystrix的feign,如果是就走上面的逻辑,否则就走下面的逻辑,降级熔断啊之类的逻辑,但我们注意到不论走哪个,最终都会走feign.target(target);
7.然后我们进入 feign.target 这个方法:
我们继续进入newInstance这个方法:
上面的三个方法没看懂,我们暂且跳过,看到target通过断点我们可以知道,type就是IService,getMethods就是获取到了FeignClient那个接口下面的所有方法了,现在就是遍历所有方法了,我们现在懂点看下遍历中做的逻辑是什么
8.首先进行判断,看他是对象吗,显然这个是方法不是对象所以跳过,然后Util.isDefault(method)我们看下:
public static boolean isDefault(Method method) {
// Default methods are public non-abstract, non-synthetic, and non-static instance methods
// declared in an interface.
// method.isDefault() is not sufficient for our usage as it does not check
// for synthetic methods. As a result, it picks up overridden methods as well as actual default methods.
final int SYNTHETIC = 0x00001000;
return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) ==
Modifier.PUBLIC) && method.getDeclaringClass().isInterface();
}
看着大致意思就是判断这个是不是接口而且修饰符必须是这几种;
所以这个判断也没进去,最后执行了下面这个方法:
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
我们查看下Feign.configKey(target.type(), method) 这个是什么:
发现是这个FeignClient接口的名称#方法名称;
所以这里就在methodToHandler.put如一个方法,还有方法的Handler,所以说这个接口虽然没有实现类,但是转发到了其他的实现类中,那么这个实现类到底是什么,我们继续往下面看:
然后就把这个handler作为参数创建了一个代理proxy并返回,然后如果我们通过Feign访问的话其实访问的就是代理proxy,但是最终干活的其实就是handler。