OpenFeign源码学习

您所在的位置:网站首页 @feignclient注解fallback OpenFeign源码学习

OpenFeign源码学习

2024-07-08 14:30| 来源: 网络整理| 查看: 265

一、在SpringBoot中使用OpenFeign

1、在 pom.xml 文件中添加 openfeign 的 starter

org.springframework.cloud spring-cloud-starter-openfeign 2.1.3.RELEASE

2、在启动类上加上 @EnableFeignClients 注解

@SpringBootApplication @EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args) } }

3、在接口上增加 @FeignClient 注解

@FeignClient(name = "user-service", url = "http://127.0.0.1:8001/user-service") public interface IUserFeign { @GetMapping("/user/queryUserName") String queryUserName(@RequestParam("userId") String userId); }

4、最后在使用的地方,注入这个接口,调用方法即可

@Service public class OrderServiceImpl implements IOrderService { @Resource IUserFeign userFeign; @Override public List list() { //...省略 String userName = userFeign.queryUserName(userId); //...省略 } } 二、SpringBoot启动时,OpenFeign做了哪些事

入口从 @EnableFeignClients 注解开始

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { //...省略 }

@EnableFeignClients 注解通过 @Import(FeignClientsRegistrar.class) 导入了扫描注册类 FeignClientsRegistrar,而 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口并重写 registerBeanDefinitions() 方法,我们看下这个方法

//FeignClientsRegistrar @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }

重点看 registerFeignClients 方法

//FeignClientsRegistrar public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set basePackages; Map 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 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))); } for (String basePackage : basePackages) { Set 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 attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } } }

这个方法会扫描当前应用程序上下文中所有标记有@FeignClient注解的接口,然后调用 registerFeignClient 方法,我们继续看56行 registerFeignClient

//FeignClientsRegistrar private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be // null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }

这个方法是把bean定义注册到BeanFactory里,重点是第4行,设置BeanClass类型为 FeignClientFactoryBean,又是熟悉的 FactoryBean,这些打上@FeignClient注解的接口,在实例化的时候,其实调用的是 FeignClientFactoryBean 的 getObject 方法,我们看下这个方法

//FeignClientFactoryBean @Override public Object getObject() throws Exception { return getTarget(); } T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); //这里包含了Feign的配置信息,有兴趣的可以看看,比如超时时间,重试次数之类的 Feign.Builder builder = feign(context); 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)); } 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(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url)); }

继续看37行,targeter.target,因为 spring-cloud-starter-openfeign 默认会集成 hystrix,所以 FeignAutoConfiguration 里的 Targeter 会实例化 HystrixTargeter 图片.png 所以这里的 targeter.target 会进到 HystrixTargeter 的 target 方法

//HystrixTargeter @Override public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget 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); }

这里如果配置文件里启用了 hystrix,那么方法参数里的 feign 就会是 feign.hystrix.HystrixFeign.Builder 类型,就会跳过第5行的if,继续往下走,其实就是多了些 fallback 的逻辑,不管怎样最终都会走到 Feign 的 target 方法里

//Feign public T target(Target target) { return build().newInstance(target); }

继续,来到 ReflectiveFeign 的 newInstance 方法

//ReflectiveFeign @Override public T newInstance(Target target) { Map nameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new LinkedHashMap(); List defaultMethodHandlers = new LinkedList(); 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; }

首先第4行的方法返回的 nameToHandler 里的 MethodHandler 的类型是 SynchronousMethodHandler,这里就不展开了,然后 methodToHandler 是把 method 对应的 MethodHandler 缓存起来,缓存的就是这个 SynchronousMethodHandler,然后我们看到第20行熟悉的代理对象创建方法,我们知道重点是第3个参数 InvocationHandler,因为最终执行的就是这个 InvocationHandler 的 invoke 方法,我们继续看19行,这个 InvocationHandler 是怎么创建的,注意这里把装有 SynchronousMethodHandler 的map作为入参传了进去 如果没有启用 hystrix,来到的是 InvocationHandlerFactory 内部类 Default 里的 create 方法

//InvocationHandlerFactory -> Default @Override public InvocationHandler create(Target target, Map dispatch) { return new ReflectiveFeign.FeignInvocationHandler(target, dispatch); }

可以看到最终创建的是 FeignInvocationHandler 对象,而装有 SynchronousMethodHandler 的map正是其成员变量

三、OpenFeign是如何调用远程接口的

前面我们知道,SpringBoot启动时,会为包含注解@FeignClient的接口创建代理对象,而调用这些接口的方法,最终会调用 FeignInvocationHandler 的 invoke 方法,我们看下

//FeignInvocationHandler @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } return dispatch.get(method).invoke(args); }

可以看到,最终会从装有 SynchronousMethodHandler 的map中取出 SynchronousMethodHandler,然后执行其 invoke 方法,我们看下 SynchronousMethodHandler 的 invoke 方法

//SynchronousMethodHandler @Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template); } 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; } } }

继续第8行 executeAndDecode 方法

//SynchronousMethodHandler Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { response = client.execute(request, options); } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) { response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); } if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } if (response.status() >= 200 && response.status() return null; } else { Object result = decode(response); shouldClose = closeAfterDecode; return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { Object result = decode(response); shouldClose = closeAfterDecode; return result; } else { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } } }

已经很明显了,跟进11行 response = client.execute(request, options);

//Client @Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); }

可以看到 convertAndSend 里就是最终发送http请求的代码

四、总结

1、在SpringBoot项目启动阶段,扫描所有@FeignClient注解标注的接口,为其创建代理对象,把接口中的方法交由 FeignInvocationHandler 处理。 2、当客户端调用该代理对象上的方法时,FeignInvocationHandler 会将方法调用转化为http请求,发送给服务端。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3