SpringCloud微服务项目之间调用是通过httprest请求来进行服务调用的,之前我们会用到HttpClient等工具来进行服务请求,Spring对这种请求进行了处理,封装成了可声明式的web客户端,使得编写web客户端更容易,feign还支持可插拔的编码器和解码器,Spring在用的时候增加了对@requestMapping的处理,同时,SpringCloud还对feign集成了注册中心(eureka)和客户端负载均衡(ribbon),了解springcloud架构可以加求求:三五三六二四七二五九,使得我们拥有一个客户端负载均衡的web请求客户端。
Feign源码分析
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 扫描本项目里面的java文件,把bean对象封装成BeanDefinitiaon对象,然后调用DefaultListableBeanFactory#registerBeanDefinition()方法把beanName放到DefaultListableBeanFactory 的 List<String> beanDefinitionNames 中去 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); // 在这里调用到FeignClientsRegistrar对象的registerBeanDefinitions()方法 invokeBeanFactoryPostProcessors(beanFactory); //从DefaultListableBeanFactory里面的beanDefinitionNames中找到所有实现了BeanPostProcessor接口的方法,如果有排序进行排序后放到list中 registerBeanPostProcessors(beanFactory); //Spring的国际化 initMessageSource(); // initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // registerListeners(); // Spring的IOC、ID处理。Spring的AOP。事务都是在IOC完成之后调用了BeanPostProcessor#postProcessBeforeInitialization()和postProcessBeforeInitialization()方法,AOP(事务)就是在这里处理的 finishBeanFactoryInitialization(beanFactory); // 执行完之后调用实现了所有LifecycleProcessor接口的类的onRefresh()方法,同时调用所有观察了ApplicationEvent接口的事件(观察者模式) finishRefresh(); } catch (BeansException ex) { // 找到所有实现了DisposableBean接口的方法,调用了destroy()方法,这就是bean的销毁 destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }
根据上面整理的代码发现,FeignClientsRegistrar#registerBeanDefinitions()方法是在扫描完bean之后,只放了一个beanname的情况下, 并没有进行IOC注册的时候调用的,这就是Spring动态扩展Bean,实现BeanDefinitionRegistryPostProcessor接口的所有方法也会在这里调用下postProcessBeanDefinitionRegistry()方法。关于Spring的东西就分析到这里。下面回到正题,分析FeignClientsRegistrar#registerBeanDefinitions()方法:
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry);//扫描EnableFeignClients标签里配置的信息,注册到beanDefinitionNames中。 registerFeignClients(metadata, registry); } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); //省略代码...根据EnableFeignClients配置的basePackages找到包下所有FeignClient注解的类,Spring的Commponet也是这么干的 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); /** * 关键地方:Feign子容器概念: * 在注入FeignAutoConfiguration类的时候,注入了一个FeignContext对象,这个就是Feign的子容器。 * 这里面装了List<FeignClientSpecification>对象,FeignClientSpecification对象的实质就是在@feignClient上配置的name为key,value为configuration对象的值 * 比如feignclient 这样配置的@FeignClient(url="https://api.weixin.qq.com",name="${usercenter.name}", configuration = UserCenterFeignConfiguration.class, primary= false) * 那么在FeignContext中就会出现一个FeignClientSpecification{name='sms-server', configuration=[class com.jfbank.sms.configuration.FeignConfiguration]}这样的数据。 * 这个地方比较关键,主要是因为后期对feign客户端的编码解码会用到自定义的类 */ //这个方法就是在ioc容器中塞入一个FeignClientSpecification对象,从而构建FeignContext子容器。 registerClientConfiguration(registry, name, attributes.get("configuration")); //重点分析这个 registerFeignClient(registry, annotationMetadata, attributes); } } } } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class);//对FeignClientFactoryBean对象生成一个BeanDefinition对象 ...读取配置 String alias = name + "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 }); //注册到beanDefinitionNames中对象 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);// }
读过Dubbo源码的同学都知道,当在DubboNamespaceHandler中解析reference标签的时候,传入了一个ReferenceBean对象,把xml中配置的属性都塞到这个对象上,也是装到了beanDefinitionNames中,然后发现ReferenceBean类和FeignClientFactoryBean都实现了FactoryBean的接口,并且里面都有getObject()和getObjectType()方法。当接口调用到这个feign客户端的时候,会从IOC中读取这个FeignClientFactoryBean并且调用getObject方法。下面就是分析getObject方法:
@Override public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); //从上文中的子容器中获取编码器,解码器等自定义类,然后封装一个Feign.Builder类 Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) {//当@FeignClient没有配置url的时候 String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url));//集成了ribbon客户端负载均衡,下一篇分析 } //当@FeignClient配置了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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }
首先看配置了url的,指定了url的feignclient解析,一直跟着代码跟到了Feign.Builder#target()方法:
public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }
直接看ReflectiveFeign#newInstance()方法:
//ReflectiveFeign#newInstance() public <T> T newInstance(Target<T> target) { //动态代理的handler类目前穿进来的是ParseHandlersByName类,所以这里要看ParseHandlersByName#apply()直接看下一个方法 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)) {//默认方法会走到这里,比如toString(),hashCode()等方法 DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else {//这里才是装配的调用类,上文分析到计息的handler是SynchronousMethodHandler#invoke() 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);//jdk动态代理 for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; } //ParseHandlersByName#apply类,构建动态代理的handler 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);//通过自定义的encoder去解析参数 } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);//通过自定义的encoder去解析参数 } else { buildTemplate = new BuildTemplateByResolvingArgs(md); } //创建handler,再看Factory#create()方法,下一个方法 result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; } //Factory#create(),构建一个SynchronousMethodHandler去处理请求,调用invoke方法 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); } //SynchronousMethodHandler#invoke()方法:实际调用的方法 //@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv);//构建requestTemplate对象 Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template);//下面不分析了,就是执行execute方法并且解码饭后返回值 } catch (RetryableException e) { retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
相关推荐
基于Spring Cloud的微服务分布式企业级B2B2C商城系统设计源码,该项目包含1562个文件,主要文件类型有520个java源文件,以及335个png图像文件。此外,还包括273个javascript文件,以及135个vue前端文件。该项目是一...
下面我们将在springcloud微服务项目中,使用redis实现简单高效的session共享。 了解springcloud架构可以加求求:三五三六二四七二五九 新建一个spring boot项目,命名springcloud-session-redis POM依赖配置 4.0.0...
php+mysql仿京东商城,B2B2C电子商务-打通电商全平台,多店铺统一管理 用户注册和登录 商品管理(产品列表、产品详情、商品分类) 订单管理(订单创建、支付、物流追踪) 会员中心(用户信息、订单历史、收藏商品...
axis2c-bin-1.6.0-linux.tar.gz axis2c-bin-1.6.0-win32.zip axis2c-src-1.6.0.tar.gz axis2c-src-1.6.0.zip 加md5
B2B2C电子商务模式是一种新的网络购物商业模式,将B2B和B2C模式完美地结合起来,提供统一的服务。该模式中,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。B2B2C模式具有...
uniapp的一个小项目,会vue的可直接上手。
"B2B2C电子商务模式探讨" 随着互联网技术的不断发展,电子商务模式也在不断创新和完善。其中,B2B2C 作为一种独特的商业模式,已经在电商领域中得到了广泛应用。这种模式不仅为消费者提供了便捷的购物体验,也为...
恒生 B2B2C 电子商务平台解决方案电子商务解决方案 本解决方案是一种全面、高效的电子商务解决方案,旨在满足不同行业、不同规模的企业在电子商务领域的全方位需求。该解决方案凭借其灵活性强、安全稳定、便捷高效...
《B2B2C电子商务模式探讨》 B2B2C电子商务模式,是现代商业领域中的一种创新性网络购物模式,它将B2B(Business to Business)和B2C(Business to Consumer)的优点相结合,形成了一种独特的商业运作方式。这种模式...
【标题】"B2B2C商城源码.zip"揭示了这是一个包含B2B2C电子商务平台源代码的压缩文件。B2B2C(Business-to-Business-to-Consumer)模式是电商领域的一种商业模式,它整合了企业对企业(B2B)和企业对消费者(B2C)的...
【标题】"基于PHP的UQCMS云商B2B2C电子商务系统"是指一个采用PHP编程语言开发的电子商务解决方案,特别设计用于构建B2B2C(Business-to-Business-to-Consumer)类型的在线市场平台。这样的系统允许企业对企业和消费...
一个基于Spring Cloud、Nacos、Seata、Mysql、Redis、RocketMQ、canal、ElasticSearch、minio的微服务B2B2C电商商城系统,采用主流的互联网技术架构、全新的UI设计、支持集群部署、服务注册和发现以及拥有完整的订单...
在电子商务领域,B2B2C(Business-to-Business-to-Consumer)模式是一种结合了企业对企业(B2B)和企业对消费者(B2C)的商业模式。B2B2C电子商务网站系统解决方案旨在帮助企业搭建一个既能进行批发交易,也能进行...
### 电子商务新模式_B2B2C应用_服装版 #### 模式的背景及意义 《电子商务新模式_B2B2C应用_服装版》一文由王秀才和马大力撰写,发布于2007年。该文章主要探讨了一种新兴的电子商务模式——B2B2C(Business-to-...
【PHP实例开发源码-UQCMS云商B2B2C电子商务系统】是一个基于PHP编程语言的开源电子商务解决方案,主要用于构建多商家、多用户参与的在线交易平台。B2B2C(Business-to-Business-to-Consumer)模式是现代电商领域中的...
SpringCloud微服务电商实战项目是当前电子商务发展的热点话题, 本文档将从畅购商场的角度,透视SpringCloud微服务电商实战项目的框架搭建、电商系统技术特点、主电商模式等方面对 SpringCloud 微服务电商实战项目...
一个基于Spring Cloud、Nacos、Seata、Mysql、Redis、RocketMQ、canal、ElasticSearch、minio的微服务B2B2C电商商城系统,采用主流的互联网技术架构、全新的UI设计、支持集群部署、服务注册和发现以及拥有完整的订单...
本商城是基于Spring Cloud、Nacos、Seata、Mysql、Redis、RocketMQ、canal、ElasticSearch、minio的微服务B2B2C电商商城系统,采用主流的互联网技术架构、全新的UI设计、支持集群部署、服务注册和发现以及拥有完整的...