“不积跬步,无以至千里。”
回顾一下,前面说了,把Feign.Builder、context、HardCodedTarget 一起传入了一个loadBalance()
方法里面,最后给 return 了,这个 return 的就是feign client的代理对象,然后注入给Controller的属性
这篇文章来着重分析一下 loadBalance() 方法
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
Client client = getOptional(context, Client.class);
上来从context(spring容器)中拿出来一个client,什么东东呢?就是Feign.Client,可见这个也是通过自动配置类注入的,找啊找,你把FeignAutoConfiguration、FeignClientsConfiguration这几个配置类都找遍了也没发现,最后在一个FeignRibbonClientAutoConfiguration
的配置类中似乎是找到了
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
public Options() {
this(10000, 60000);
}
发现这竟然是默认的… … 真是找了个寂寞!!!
到这里,没什么线索,就要思考一下,@FeignClient的注解里面配置的不是url,而是服务名称,所以这一块肯定是跟ribbon整合的
org.springframework.cloud.netflix.feign.ribbon
结果在这个包下面,找到一个 DefaultFeignLoadBalancedConfiguration
!!!
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
这里spring容器中拿到的就是这个LoadBalancerFeignClient
,拿到之后也给设置到 builder 中去了
然后 Targeter targeter = get(context, Targeter.class);
显然,就是从context 中拿到一个Targeter
,然后我们在FeignAutoConfiguration
这个配置类里面可以找到这个Targeter 的自动配置,代码如下
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
居然有两个,那么用的是哪一个呢?实际上是 HystrixTargeter
,因为 feign.hystrix.HystrixFeign 这个类存在的,就在 feign-hystrix-9.7.0.jar 这个jar包里面
那么既然用的是这个所谓的HystrixFeign,是不是可以猜想,feign调用的时候会默认和Hystrix整合的,然后可以去使用Hystrix的熔断、降级的特性
其实springcloud的几个组件,都是有一些整合在里面的,比如说feign 就整合了 ribbon和hystrix用来做负载和熔断,ribbon呢,又跟eureka整合,去获取和更新服务列表,正所谓一通百通,也是这个意思,理解了其中一些组件,再看另外的组件,就不会吃力了
这里明显openFeign默认是集成Hystrix的,其实在早先的版本,springcloud-feign组件的时候,是需要配置一个feign.hystrix.enabled
参数,才会使用 HystrixTargeter 的,否则使用的就是feign 默认的 DefaultTargeter
,现在openfeign 对其做了改进
代码接着走 targeter.target(this, builder, context, target);
断点就来到了 HystrixTargeter的 target方法
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
}
!(feign instanceof feign.hystrix.HystrixFeign.Builder)
但是因为我们的Feign.Builder
使用的不是Hystrix.Builder,而是默认的Feign.Builder
所以 feign.target(target) ,使用的还是默认的,没有整合Hystrix!!!
可以我们之前的猜想Feign默认整合 Hystrix组件,并不是正确的!还是需要配置feign.hystrix.enabled
的参数才能整合进来,分析了个寂寞
接着走
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
@SuppressWarnings("unchecked")
@Override
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;
}
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
public Map<String, MethodHandler> apply(Target key) {
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
result.put(md.configKey(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
}
key.type() 这个Target 就是之前传入的HardCodedTarget
,里面的type 就是feignclient 的接口类型
List metadata = contract.parseAndValidatateMetadata(key.type());
解析的metadata 是一个MethodMetadata
的集合,而这个MethodMetadata 里面包含的就是每个接口方法的一些元数据信息,最主要包含了方法名、返回值类型、http发送模板还有一些其他的东西
看下这个template GET /test_1/{param} HTTP/1.1 这不就是SpringMvcContract组件从spring的注解中解析出来的吗?
接着会使用我们之前传入Feign.Builder 中的Encoder、Decoder、Contract、包括这个target ,去解析接口方法,为什么会使用这些组件呢?很明白的,接口注解都是spring web mvc的注解,我们这个Contract是啥呢?就是SpringMvcContract!!!
解析之后,拿到一个nameToHandler ,可以看到,这是一个Map,map的key就是方法名,value就是SynchronousMethodHandler
对象,这是一个代理方法处理组件,就是最终用来处理这个接口方法的组件,可以看到,里面注入了很多需要的组件(target、client、retryer、日志、编解码器等)
result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
public MethodHandler create(Target<?> target, MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
Options options, Decoder decoder, ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, decode404, closeAfterDecode);
}
targetToHandlersByName
的apply方法,返回的就是这个result
然后接着走 methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
很明显,这个代码,就是把nameToHandler 中的SynchronousMethodHandler 这个方法处理的组件转移到了新的Map(methodToHandler)中罢了,只不过先前的Map key是方法名称,现在是Method 对象而已
InvocationHandler handler = factory.create(target, methodToHandler);
这里不多说,搞一个InvocationHandler ,是准备创建动态代理使用,因为稍微有点java基本的都会知道,java的动态代理,分jdk动态代理和cglib对象代理,针对接口的一般使用jdk动态代理,而底层使用的就是这个InvocationHandler
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
这里注意,把之前转移的map methodToHandler又赋值给了dispatch 这个map,记住这个点,也就是说现在dispatch 里面,key是接口的方法对象,而value就是方法处理组件SynchronousMethodHandler
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
这个就不解释了,因为是在没啥可说的,就是利用jdk动态代理创建一个代理对象,然后就把这个对象给返回了
ok,这篇文章就到这里,下一讲会来探究一下Controller里面调用代理对象的方法会怎么处理,拭目以待!