feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
环境:
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-openfeign.version>2.2.6.RELEASE</spring-cloud-openfeign.version>
本次主要是使用nacos作为服务中心,sentinel作为熔断分析
前期环境准备:SpringCloud之熔断(六)
主要相关注解:EnableFeignClients,FeignClient
在使用微服务时会开启@EnableFeignClients作为远程调用,用FeignClient作为具体调用的定义
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class NacosConsumerApplication { ...... public static void main(String[] args) { SpringApplication.run(NacosConsumerApplication.class, args); } ...... }
@FeignClient(value = "service-provider", fallback = ProviderClientFallback.class) public interface ProviderClient { @GetMapping("/echo/{param}") String echo(@PathVariable("param") String param); @GetMapping("/echo1/{param}") String echo1(@PathVariable("param") String param); }
在@EnableFeignClients的定义中会导入FeignClientsRegistrar
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { ...... }
而FeignClientsRegistrar继承了ImportBeanDefinitionRegistrar,所以汇总初始化的时候调用FeignClientsRegistrar#registerBeanDefinitions,最后会调到FeignClientsRegistrar#registerFeignClients进行扫描使用了EnableFeignClients的接口,并且注册到容器中。定义生产对象的工厂为FeignClientFactoryBean
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ...... if (clients == null || clients.length == 0) { //扫描使用了FeignClient注解的接口 ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); Set<String> basePackages = getBasePackages(metadata); for (String basePackage : basePackages) { candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } ...... for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { ...... //配置生产对象的相关bean工厂的信息 registerFeignClient(registry, annotationMetadata, attributes); } } } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //定义参数对象的工厂为FeignClientFactoryBean BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); ...... }
springCloud FeignClient其实是利用了spring的代理工厂来生成代理类,所以这里将所有的 feignClient的描述信息 BeanDefinition设定为 FeignClientFactoryBean类型,该类又继承 FactoryBean,很明显,这是一个代理类。 在spring中, FactoryBean是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的feignClient bean包装成 FeignClientFactoryBean。扫描方法到此结束。
代理类什么时候会触发生成呢? 在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean。
然后分析FeignClientFactoryBean查看创建对象的具体逻辑
在创建feignClient注解的对象的时候回调用FeignClientFactoryBean#getObject,然后到FeignClientFactoryBean#getTarget,先以上面的配置分析
<T> T getTarget() { FeignContext context = applicationContext.getBean(FeignContext.class); //获取一个Builder,默认是HystrixFeign.Builder, 因为例子使用的sentinel,所以现在获取到的是SentinelFeign.Builder Feign.Builder builder = feign(context); if (!StringUtils.hasText(url)) { if (!name.startsWith("http")) { url = "http://" + name; } else { url = name; } url += cleanPath(); //返回代理对象 return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url)); } ...... }
获取Feign.Builder函数
protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(type); // @formatter:off //在SentinelFeignAutoConfiguration会注入Feign.Builder 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; }
先看看是怎么注入SentinelFeign.Builder的在配置文件中我们配置了application.properties
feign.sentinel.enabled=true
然后在配置文件中SentinelFeignAutoConfiguration中会注入对象
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ SphU.class, Feign.class }) public class SentinelFeignAutoConfiguration { @Bean @Scope("prototype")//表示每次获得bean都会生成一个新的对象 @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.sentinel.enabled")//在配置文件中已经打开,如果havingValue未指定值,默认情况下在属性配置中设置的值为true则生效 public Feign.Builder feignSentinelBuilder() { return SentinelFeign.builder(); } }
继续分析FeignClientFactoryBean#loadBalance创建过程
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //LoadBalancerFeignClient对象,默认是Client.Default实现网络请求,也可以配置为OkHttpClient,ApacheHttpClient等 Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); //HystrixTargeter对象 Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, target); } ...... }
在进行看HystrixTargeter#target
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { //因为我们使用的是SentinelFeign.Builder,所以执行此处 return feign.target(target); } ...... }
在执行到ReflectiveFeign#newInstance
public <T> T newInstance(Target<T> target) { //会获取接口方法和方法代理SynchronousMethodHandler对象的一个Map Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); ...... //返回一个SentinelInvocationHandler对象 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; }
查看创建SentinelInvocationHandler的过程,最终会走到SentinelFeign#Builder#build#new InvocationHandlerFactory#create进行创建
public Feign build() { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { ...... //此处就是配置异常的处理方式,不管配置fallback或者fallbackFactory,最后都是FallbackFactory进行处理 Object fallbackInstance; FallbackFactory fallbackFactoryInstance; // check fallback and fallbackFactory properties if (void.class != fallback) { fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type()); return new SentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance)); } if (void.class != fallbackFactory) { fallbackFactoryInstance = (FallbackFactory) getFromContext( beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class); return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance); } return new SentinelInvocationHandler(target, dispatch); } ...... }); super.contract(new SentinelContractHolder(contract)); return super.build(); }
所以最终调用FeignClient访问接口的时候会调用SentinelInvocationHandler的代理对象
然后查看代理对象的invoke方法
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { ...... Object result; //根据方法找到对应的SynchronousMethodHandler MethodHandler methodHandler = this.dispatch.get(method); // only handle by HardCodedTarget if (target instanceof Target.HardCodedTarget) { Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP .get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method)); // resource default is HttpMethod:protocol://url if (methodMetadata == null) { result = methodHandler.invoke(args); } else { String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url() + methodMetadata.template().path(); Entry entry = null; try { ContextUtil.enter(resourceName); entry = SphU.entry(resourceName, EntryType.OUT, 1, args); //执行方法 result = methodHandler.invoke(args); } catch (Throwable ex) { // fallback handle if (!BlockException.isBlockException(ex)) { Tracer.trace(ex); } //此处就是通过FeignClient注解配置的fallback或者fallbackFactory进行创建的FallbackFactory对象,也就是熔断的处理 if (fallbackFactory != null) { try { Object fallbackResult = fallbackMethodMap.get(method) .invoke(fallbackFactory.create(ex), args); return fallbackResult; } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an // interface throw new AssertionError(e); } catch (InvocationTargetException e) { throw new AssertionError(e.getCause()); } } ...... } ...... } } else { // other target type using default strategy result = methodHandler.invoke(args); } return result; }
总结
使用FeignClient进行微服务访问,就是自动创建一个代理对象进行远程调用,同时可以配置访问方式和异常处理等到