1.SOFA RPC源码解析
1.1Rest服务
1.1.1 服务引用
以SOFABoot自带的RPC案例sofaboot-sample-with-rpc为例,详细描述SOFABoot服务发布原理。
在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。
RPC案例的SpringXML配置文件内容如下:
1. <?xml version="1.0"encoding="UTF-8"?>
2. <beansxmlns="http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns:sofa="http://sofastack.io/schema/sofaboot"
5. xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
6. http://sofastack.io/schema/sofaboot http://sofastack.io/schema/sofaboot.xsd"
7. default-autowire="byName">
8.
9. <beanid="personServiceImpl"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/>
10.
11. <sofa:serviceref="personServiceImpl"interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
12. <sofa:binding.rest/>
13. </sofa:service>
14.
15. <sofa:referenceid="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"jvm-first="false">
16. <sofa:binding.rest>
17. <sofa:global-attrstimeout="300000" address-wait-time="2000"/>
18. <sofa:routetarget-url="127.0.0.1:8341"/>
19. <sofa:methodname="sayName" timeout="300000"/>
20. </sofa:binding.rest>
21. </sofa:reference>
22.
23. <bean id="personFilter"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/>
24.
25. </beans>
在SpringXML配置文件中:
1. 通过XML元素bean定义PersonService接口实现类PersonServiceImpl;
2. 通过XML元素sofa:reference引用restful格式的PersonService接口服务;
启动本应用,启动流程在《启动原理》中已经详细描述,在此不再详述,直接到从SpringXML配置文件加载Bean定义开始分析。
一、 注册ReferenceFactoryBean类对应的Bean定义
调用AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh())方法刷新Spring应用上下文过程,通过invokeBeanFactoryPostProcessors(beanFactory)方法调用beanFactory中所有实现了BeanFactoryPostProcessor接口的类。
当调用到ConfigurationClassPostProcessor类processConfigBeanDefinitions方法时,在循环处理@Configuration配置类过程中,调用ConfigurationClassBeanDefinitionReader类loadBeanDefinitions(configClasses)方法,处理解析完成的@Configuration配置类。此时,其中有一步是调用ConfigurationClassBeanDefinitionReader类loadBeanDefinitionsFromImportedResources(configClass.getImportedResources())方法,加载@ImportResource注解里面配置的SpringXML配置文件中定义的Bean。在这个方法中,使用XMLBeanDefinitionReader加载SpringXML配置文件中定义的Bean。
接下来就是SpringXML配置文件中各种标签的解析过程。
对于XML标签bean,由于是最基本的SpringXML标签,大家都应用比较熟悉了,在此不再详述。
对于XML标签sofa:reference,其处理过程如下:
XMLBeanDefinitionReader类调用DefaultBeanDefinitionDocumentReader类registerBeanDefinitions方法注册Bean定义;
DefaultBeanDefinitionDocumentReader类调用BeanDefinitionParserDelegate类parseCustomElement方法解析自定义的XML元素。
在此,对于XML标签sofa:service,根据命名空间sofa对应的值http://sofastack.io/schema/sofaboot,在spring.handlers(此文件位于infra-sofa-boot-starter.jar)文件中,查找到XML标签的处理类SofaBootNamespaceHandler:
1. http\://sofastack.io/schema/sofaboot=com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler
在SofaBootNamespaceHandler类中,调用findParserForElement方法,查找指定XML元素的解析类,此处service对应的BeanDefinitionParser解析类为com.alipay.sofa.runtime.spring.parser.ReferenceDefinitionParser。
使用ReferenceDefinitionParser类把SpringXML配置文件中sofa:reference标签定义的服务转换为ReferenceFactoryBean,并注册到Spring应用上下文中。
二、 创建ReferenceFactoryBean类的实例
调用AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh())方法刷新Spring应用上下文。
当通过onRefresh() 方法调用createEmbeddedServletContainer方法,在调用DefaultListableBeanFactory类doGetBeanNamesForType(ResolvableType type, booleanincludeNonSingletons, boolean allowEagerInit)方法,获取类型为org.springframework.boot.context.embedded.EmbeddedServletContainerFactory的Bean名字时,会遍历beanFactory中所有beanDefinitionNames,判断每个beanName所代表的Bean定义RootBeanDefinition中beanClass是否与EmbeddedServletContainerFactory类型相匹配。当调用org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch方法判断名为personReferenceRest的BeanDefinition对应的实例与EmbeddedServletContainerFactory是否类型匹配。由于XML标签sofa:reference被解析为ReferenceFactoryBean类,该类实现了FactoryBean接口,表明其为工厂Bean,所以为了查看此工厂Bean创建的实例的具体类型,必须实例化此工厂Bean,即ReferenceFactoryBean。至此,又会调用AbstractBeanFactory的getObject方法,获取此ReferenceFactoryBean创建的具体实例。
当实例化ReferenceFactoryBean时,由于ReferenceFactoryBean实现了Initializing接口,所以在调用ReferenceFactoryBean类的初始化方法时,会调用ReferenceFactoryBean类的afterPropertiesSet方法,进行实例的初始化操作。
到现在为止,开始Rest服务引用流程:
1. 根据XML标签sofa:reference所包含的子标签sofa:binding.*(一个或多个,此处是sofa:binding.rest),解析出引用服务的类型,此处是restful风格的服务。
2. 创建Reference接口的实现ReferenceImpl,其中,引用的服务接口类型为com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService,接口模式为spring(表明此引用服务来自于SpringXML配置文件),jvmFirst为true(表示优先使用JVM服务,即在同一个JVM内部服务之间的调用方式)。
3. 把解析出的binding(此处为RestBinding)增加到reference实例的属性bindings集合中。
4. 调用ReferenceRegisterHelper类registerReference方法:
1. public static ObjectregisterReference(Reference reference,
2. BindingAdapterFactorybindingAdapterFactory,
3. SofaRuntimeProperties sofaRuntimeProperties,
4. SofaRuntimeContextsofaRuntimeContext) {
5. Binding binding = (Binding)reference.getBindings().toArray()[0];
6.
7. if(!binding.getBindingType().equals(JvmBinding.JVM_BINDING_TYPE)
8. &&!sofaRuntimeProperties.isDisableJvmFirst() && reference.isJvmFirst()) {
9. reference.addBinding(new JvmBinding());
10. }
11.
12. ComponentManager componentManager= sofaRuntimeContext.getComponentManager();
13. ReferenceComponent referenceComponent =new ReferenceComponent(reference,
14. new DefaultImplementation(),bindingAdapterFactory, sofaRuntimeProperties,
15. sofaRuntimeContext);
16.
17. if(componentManager.isRegistered(referenceComponent.getName())) {
18. returncomponentManager.getComponentInfo(referenceComponent.getName())
19. .getImplementation().getTarget();
20. }
21.
22. ComponentInfo componentInfo =componentManager.registerAndGet(referenceComponent);
23. returncomponentInfo.getImplementation().getTarget();
24.
25. }
注册Reference组件的主要步骤:
- 获取ReferenceImpl实例的属性bindings集合中第一个binding;(此处为RestBinding类型)
- 此binding不是JvmBinding类型,但属性disableJvmFirst为false(表明全局开启jvmFirst),属性jvmFirst为false,即不优先使用JVM服务,不用增加JvmBinding到reference实例的属性bindings集合中;
- 从sofaRuntimeContext实例中获取组件管理器实例componentManager;
- 创建ReferenceComponent实例;
- 判断组件管理器componentManager中是否存在指定名字的组件,例如:名称为reference:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService:#-1745597079,如果有,则直接从组件管理器获取组件,并返回组件的实现的代理。如果没有,则在组件管理器中注册刚才创建的ReferenceComponent实例。