经过上篇咱们了解OpenFeign他也能够完成远程通讯,可是它并非真正义意上的RPC通讯,由于他是经过封装代理来实现的,下面和之前同样,知道了怎么用就来看下他是怎么实现的。spring
有了ribbon的铺垫如今看OpenFeign应该很清楚的知道,这玩意就是经过注解拿到服务名,而后经过服务名获取服务列表,进行解析和负载最终拼接出一个URI路径进行代理请求,那么他要完成这一系列动做他就要作下面几件事。app
看过我写的ribbon的应该清楚,若是想要找到进入源码的入口那么应该要找的是FeignClient,可是FeignClient是在哪里被解析的呢,在应用篇中我在启动类中加了个@EnableFeignClients注解,这 个注解的做用其实就是开启了一个FeignClient的扫描,那么点击启动类的@EnableFeignClients注解看下他是怎么开启FeignClient的扫描的,进去后发现里面有个@Import(FeignClientsRegistrar.class)这个FeignClientsRegistrar跟Bean的动态装载有关负载均衡
点击进去有个registerBeanDefinitions方法经过名称能够知道是一个Bean的注入方法框架
下面我写一个简单的例子来描述他是如何实现动态加载的,学FeignClientsRegistrar类 implements ImportBeanDefinitionRegistrar接口并实现registerBeanDefinitions方法ide
这一步搞完后,定义一个注解,把@EnableFeignClients注解上的注解都抄过来并把@Import注解里面的类改为咱们本身定义的类源码分析
而后在启动类上用上自定义的注解,那么在启动类时就能够进行一个Bean的动态装载了ui
经过这个概念已经很清楚源码中FeignClientsRegistrar类的FeignClientsRegistrar是怎么完成Bean的动态加载了this
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册默认配置信息 registerDefaultConfiguration(metadata, registry);
//注册FeignClients(可能有多个) registerFeignClients(metadata, registry); }
进入registerFeignClients(metadata, registry);这玩意是干啥的呢,在启动类中的@EnableFeignClients是能够定义多个basePackers的若是定义了多个那就要扫描FeignClients,下面就是扫描处理过程,看过spring源码的人就知道前面是什么注解解析编码
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } //bssePackages是解析不一样basePackage路径下的FeignClient的声明 for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface 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 = getClientName(attributes);
//若是每一个包路径下都有一个或多个FeignClient的话就要加载 一次动态configuration配置因此这就是为何前面已经加载过了这里还要加载一次的缘由 registerClientConfiguration(registry, name, attributes.get("configuration")); //注册Feignclient registerFeignClient(registry, annotationMetadata, attributes); } } } }
点击registerFeignClient(registry, annotationMetadata, attributes);看下作了啥事,这里面的逻辑其实就干了一件事,就是经过BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);注入一个Bean;这个注入的过程当中有个比较重要的代码BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);这是一个构造者,构造一个BeanDefinition,里面把FeignClientFactoryBean.class给传了进去url
下面进入.genericBeanDefinition(FeignClientFactoryBean.class);看把 FeignClientFactoryBean.class类传进去干吗,发现注册的Bean就是参数中本身传进来的beanClass,这个传进去的beanClass是工厂Bean
Spring Cloud FengnClient其实是利用Spring的代理工厂来生成代理类,因此在这里地方才会把全部的FeignClient的BeanDefifinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来建立代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来讲, 他逻辑上是感知不到这个 Bean 是普通的 Bean 仍是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 自己, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。
点击这个工厂Bean的FeignClientFactoryBean类中发现里面有个getObject()方法,这个工厂Bean就是经过这个getTarget();返回一个真正的实例
画下时序图
前面说到了在启动时会经过@EnableFeignClients去扫描全部指定路径下的@FeignClient注解声明的一个接口,而后在扫描到之后要去生成一个动态代理的类,这个动态代理的生成就是在调用getObject()时完成 ,并且getObject()又会调用他方法里面的getTarget()去完成这件事,
<T> T getTarget() {
//FeignContext注册到容器是在FeignAutoConfiguration上完成的
//在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的
//来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);//构建Builder对象
//若是url为空,则走负载均衡,生成有负载均衡功能的代理类
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
//若是指定了url,则生成默认的代理类
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
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();
}
builder.client(client);
}//生成默认代理类
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
上面有段代码Feign.Builder builder = feign(context);是构建Builder对象
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; }
上面的builder构造完后继续向下走,配置完Feign.Builder以后,再判断是否须要LoadBalance,若是须要,则经过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
//针对某一个服务的client Client client = getOptional(context, Client.class); if (client != null) {
//将client设置进去至关于增长了客户端负载均衡解析的机制 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?"); }
点击上图的targeter.target(this, builder, context, target);由于熔断准备在后面讲,因此在选tartget的实现时选择DefaultTarget.target
点击feign.target()往下走,走到这里其实就已经到了核心逻辑了,前面不一直说动态代理吗,前面走的都是人生最长的套路,前面本身写的控制层代码经过@Resource注解注入的UserOpenFeign他最终会调用下面的方法返回一个实例,那么下面看下这newInstance()方法作了啥,发现这玩意有两个实现,至于选择哪一个就要看build()返回的是什么了,向下看发现build()返回的是ReflectiveFeign,因此选第二个
public <T> T newInstance(Target<T> target) { //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式 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))); } } // 基于Proxy.newProxyInstance 为接口类建立动态实现,将全部的请求转换给InvocationHandler 处理。 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; }
下面经过debugger验证下,会看到userOpenFeign的返回的是代理类,经过下图能够知道当调用userOpenFeign时他实际上是调用ReflectiveFeign中的handler,而经过Debugger发现这个handler是FeginInvocationHandler,
居然是走了代理那么他必定是走了ReflectiveFeign的代理方法invoke()方法