OpenFeign源码学习 |
您所在的位置:网站首页 › @feignclient注解fallback › OpenFeign源码学习 |
一、在SpringBoot中使用OpenFeign
1、在 pom.xml 文件中添加 openfeign 的 starter org.springframework.cloud spring-cloud-starter-openfeign 2.1.3.RELEASE2、在启动类上加上 @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 这里如果配置文件里启用了 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 |