1.SOFABoot源码解析
1.1 JVM服务
1.1.1 服务发布
以SOFABoot自带的RPC案例sofaboot-sample-with-rpc为例,详细描述SOFABoot JVM服务发布过程。
在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。
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. <bean id="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.jvm/>
13. </sofa:service>
14.
15. <sofa:referenceid="personReferenceRest"interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
16. <sofa:binding.jvm/>
17. </sofa:reference>
18.
19. <bean id="personFilter"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/>
20.
21. </beans>
在SpringXML配置文件中:
1. 通过XML元素bean定义PersonService接口实现类PersonServiceImpl;
2. 通过XML元素sofa:service以jvm格式发布PersonService接口服务;
启动本应用,启动流程在《启动原理》中已经详细描述,在此不再详述,直接从SpringXML配置文件加载Bean定义开始分析。
一、 注册ServiceFactoryBean类对应的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:service,其处理过程如下:
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.ServiceDefinitionParser。
使用ServiceDefinitionParser类把SpringXML配置文件中sofa:service标签定义的服务转换为ServiceFactoryBean,并注册到Spring应用上下文中。
二、 创建ServiceFactoryBean类的实例
调用AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh())方法刷新Spring应用上下文过程,通过finishBeanFactoryInitialization(beanFactory)方法实例化beanFactory中所有剩余的非延迟初始化的单例对象。
当实例化ServiceFactoryBean时,由于ServiceFactoryBean实现了Initializing接口,所以在调用ServiceFactoryBean类的初始化方法时,会调用ServiceFactoryBean类的afterPropertiesSet方法,进行实例的初始化操作。
到现在为止,开始JVM服务发布流程:
1. 根据XML标签sofa:service所包含的子标签sofa:binding.*(一个或多个,此处是sofa:binding.jvm),解析出服务发布的类型,此处是jvm服务。
2. 创建DefaultImplementation实例implementation,并设置其属性ref为SpringXML文件中sofa-service元素的ref属性中指定的接口实现,此处为SpringXML文件中id为personServiceImpl的com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl类的实例。
3. 创建Service接口的实现ServiceImpl,其中,服务接口类型为com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService,接口模式为spring(表明此引用服务来自于SpringXML配置文件),target为ref(即id为personServiceImpl的PersonServiceImpl实例)。
4. 如果解析出的bindings为空,则把JvmBinding增加到bindings列表中。此处,由于在SpringXML文件中只配置了binding.jvm,在第一步parseBindings方法中,由于jvm没有对应的BindingConverter实现,所以导致bindings为空。
5. 把解析出的binding(此处为JvmBinding)增加到service实例的属性bindings列表中。
6. 创建ServiceComponent实例componentInfo。
7. 从sofaRuntimeContext实例中获取组件管理器实例componentManager,并调用其register方法注册刚才创建的服务组件componentInfo,具体注册操作在doRegister方法完成:
1. private ComponentInfo doRegister(ComponentInfo ci) {
2. ComponentName name = ci.getName();
3. if (isRegistered(name)) {
4. SofaLogger.error("Component was alreadyregistered: {0}", name);
5. return getComponentInfo(name);
6. }
7.
8. try {
9. ci.register();
10. } catch (Throwable e) {
11. SofaLogger.error(e, "Failed toregister component: {0}", ci.getName());
12. return null;
13. }
14.
15. SofaLogger.info("Registeringcomponent: {0}", ci.getName());
16.
17. try {
18. ComponentInfo old = registry.putIfAbsent(ci.getName(), ci);
19. if (old != null) {
20. SofaLogger.error("Component wasalready registered: {0}", name);
21. return old;
22. }
23. if (ci.resolve()) {
24. typeRegistry(ci);
25. ci.activate();
26. }
27. } catch (Throwable e) {
28. ci.exception(new Exception(e));
29. SofaLogger.error(e, "Failed tocreate the component {0}", ci.getName());
30. }
31.
32. return ci;
33. }
在组件管理器中注册组件的主要处理逻辑如下:
- 获取组件名称,此处为service:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService。
- 判断组件管理器componentManager中是否存在指定名字的组件。如果有,则直接从组件管理器获取组件,并返回。如果没有,则继续注册流程。
- 调用组件ServiceComponent类的register方法,更新组件状态为registered。
- 把组件置入组件管理器的registry中(ConcurrentMap<ComponentName, ComponentInfo>)。
- 调用组件ServiceComponent类的resolve方法,更新组件状态为resolved。
- 把组件置入组件管理器的resolvedRegistry中(ConcurrentMap<ComponentType, Map<ComponentName,ComponentInfo>>)。
- 调用组件ServiceComponent的activate方法,激活组件,并更新组件状态为activated。
1. public void activate() throwsServiceRuntimeException {
2.
3. activateBinding();
4. super.activate();
5. }
调用activateBinding方法,激活服务组件的所有Binding。
1. private void activateBinding() {
2.
3. Object target = service.getTarget();
4. ……略
5. if (service.hasBinding()) {
6. boolean allPassed = true;
7. Set<Binding> bindings =service.getBindings();
8. for(Binding binding : bindings) {
9. BindingAdapter<Binding>bindingAdapter = this.bindingAdapterFactory
10. .getBindingAdapter(binding.getBindingType());
11.
12. if (bindingAdapter == null) {
13. throw newServiceRuntimeException(……略);
14. }
15.
16. Object outBindingResult;
17. ……略
18. try {
19. outBindingResult =bindingAdapter.outBinding(service, binding, target,
20. getContext());
21. } catch (Throwable t) {
22. allPassed = false;
23. ……略
24. continue;
25. }
26. if(!Boolean.FALSE.equals(outBindingResult)) {
27. ……略
28. } else {
29. binding.setHealthy(false);
30. SofaLogger.info("<<Out Binding [{0}] Fails, Don't publish service - {1}.",
31. binding.getBindingType(),service);
32. }
33. }
34.
35. if (!allPassed) {
36. throw new ServiceRuntimeException(……略);
37. }
38. }
39.
40. SofaLogger.info("Register Service- {0}", service);
41. }
activateBinding方法主要处理逻辑如下:
- 获取服务的目标类target;
- 依次遍历服务所有Binding,分别为每种Binding发布服务,具体过程如下:根据Binding类型(此处为jvm),获取BindingAdapter接口的实现,此处为:com.alipay.sofa.runtime.service.binding.JvmBindingAdapter。调用JvmBindingAdapter类outBinding方法,对外发布服务:
1. public Object outBinding(Object contract,JvmBinding binding, Object target,
2. SofaRuntimeContextsofaRuntimeContext) {
3. return null;
4. }
由于是JVM服务,即只在同一个JVM中同一个应用的不同模块之间可以访问,同一个JVM中不同应用及JVM外部应用无法访问,所以此处outBinding方法为空方法,不对外提供任何服务。
由此可以看出,发布JVM服务只是把服务组件置入组件管理器的registry中(ConcurrentMap<ComponentName,ComponentInfo>),然后把服务组件的状态设置为activated,表示服务发布成功。
1.1.2 服务引用
以SOFABoot自带的RPC案例sofaboot-sample-with-rpc为例,详细描述SOFABoot JVM服务引用过程。
在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。
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.jvm/>
13. </sofa:service>
14.
15. <sofa:referenceid="personReferenceRest"interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
16. <sofa:binding.jvm/>
17. </sofa:reference>
18.
19. <bean id="personFilter"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/>
20.
21. </beans>
在SpringXML配置文件中:
1. 通过XML元素bean定义PersonService接口实现类PersonServiceImpl;
2. 通过XML元素sofa:reference引用jvm格式的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:reference,根据命名空间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方法,进行实例的初始化操作。
到现在为止,开始JVM服务引用流程:
1. 根据XML标签sofa:reference所包含的子标签sofa:binding.*(一个或多个,此处是sofa:binding.jvm),解析出服务引用的类型,此处是jvm服务。
2. 设置ReferenceFactoryBean的bindings属性。由于在SpringXML文件中只配置了binding.jvm,并且jvm没有对应的BindingConverter实现,所以bindings为空。
3. 创建Reference接口的实现ReferenceImpl,其中,引用的服务接口类型为com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService,接口模式为spring(表明此引用服务来自于SpringXML配置文件),jvmFirst为true(表示优先使用JVM服务,即在同一个JVM内部同一个应用不同模块之间服务调用方式)。
4. 如果bindings为空,则把JvmBinding增加到bindings;
5. 把bindings中第一个Binding(此处为JvmBinding)增加到reference实例的属性bindings集合中。
6. 调用ReferenceRegisterHelper类registerReference方法:
1. public static ObjectregisterReference(Reference reference,
2. BindingAdapterFactorybindingAdapterFactory,
3. SofaRuntimePropertiessofaRuntimeProperties,
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. return componentInfo.getImplementation().getTarget();
24.
25. }
注册Reference组件的主要步骤:
- 获取reference实例包含的binding,此处为JvmBinding类型;
- 如果binding不是JvmBinding类型,disableJvmFirst属性为false(表明全局开启jvmFirst),jvmFirst为true,则增加JvmBinding到reference实例的属性bindings集合中。此处binding为JvmBinding,所以直接跳过,继续后续流程;
- 从sofaRuntimeContext实例中获取组件管理器实例componentManager;
- 创建ReferenceComponent实例;
- 判断组件管理器componentManager中是否存在指定名字的组件,例如:名称为reference:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService:#342780926,如果有,则直接从组件管理器获取组件,并返回组件的实现的代理。如果没有,则在组件管理器中注册刚才创建的ReferenceComponent实例。具体的组件注册在doRegister方法中实现:
1. private ComponentInfodoRegister(ComponentInfo ci) {
2. ComponentName name = ci.getName();
3. if (isRegistered(name)) {
4. SofaLogger.error("Componentwas already registered: {0}", name);
5. return getComponentInfo(name);
6. }
7.
8. try {
9. ci.register();
10. } catch (Throwable e) {
11. SofaLogger.error(e, "Failed toregister component: {0}", ci.getName());
12. return null;
13. }
14.
15. SofaLogger.info("Registeringcomponent: {0}", ci.getName());
16.
17. try {
18. ComponentInfo old = registry.putIfAbsent(ci.getName(), ci);
19. if (old != null) {
20. return old;
21. }
22. if (ci.resolve()) {
23. typeRegistry(ci);
24. ci.activate();
25. }
26. } catch (Throwable e) {
27. ……略
28. }
29.
30. return ci;
31. }
在组件管理器中注册组件的主要过程如下:
- 调用组件ComponentInfo类的register方法,更新组件状态为registered。
- 把组件置入组件管理器的registry中(ConcurrentMap<ComponentName, ComponentInfo>)。
- 调用组件ComponentInfo类的resolve方法,更新组件状态为resolved。
- 把组件置入组件管理器的resolvedRegistry中(ConcurrentMap<ComponentType, Map<ComponentName,ComponentInfo>>)。
- 调用组件ComponentInfo类的activate方法,激活组件,并更新组件状态为activated。
1. public void activate() throwsServiceRuntimeException {
2. if (reference.hasBinding()) {
3. Binding candidate = null;
4. Set<Binding> bindings =reference.getBindings();
5. if (bindings.size() == 1) {
6. candidate =bindings.iterator().next();
7. } else if (bindings.size() > 1){
8. Object backupProxy = null;
9. for (Binding binding :bindings) {
10. if(JvmBinding.JVM_BINDING_TYPE.getType().equals(binding.getName())) {
11. candidate = binding;
12. } else {
13. backupProxy = createProxy(reference, binding);
14. }
15. }
16. if (candidate != null) {
17. ((JvmBinding)candidate).setBackupProxy(backupProxy);
18. }
19. }
20.
21. Object proxy = null;
22. if (candidate != null) {
23. proxy = createProxy(reference, candidate);
24. }
25.
26. this.implementation = newDefaultImplementation();
27. implementation.setTarget(proxy);
28. }
29.
30. super.activate();
31. latch.countDown();
32. }
在ComponentInfo类activate方法中,主要处理逻辑如下:
- 获取ReferenceImpl中所有Binding。
此处需要注意的是,在rpc.xsd文件中,规定XML标签sofa-reference只能有0个或1个Binding子元素。其可选子元素:binding.jvm、binding.bolt、binding.rest、binding.dubbo。
如果设置多个Binding子元素,则在实例化时抛异常。
如果不设置Binding子元素,则默认为JvmBinding。
这个隐含的规则从这部分代码中无法体现,但影响其下面的处理逻辑。
由此可知,Binding的个数只能为1或2。
当Binding的个数当为1时,则只能是JvmBinding或其他类型Binding之一。
当Binding的个数当为2时,则只能是JvmBinding和其他类型Binding之一。
在下述三种情况下,ReferenceImpl中所有Binding的个数为1:
★ 当未设置Binding子元素,而采用默认的JvmBinding;
★ 设置Binding子元素为JvmBinding;
★ 设置一个其他类型的Binding,但disableJvmFirst为false,并且jvmFirst为true。
在下述情况下,ReferenceImpl中所有Binding的个数为2:
★ 设置一个其他类型的Binding,但disableJvmFirst为true,或jvmFirst为false。
- 如果Binding个数为1,其处理逻辑为:
★ 设置candidate(候选Binding)为当前Binding;
- 如果Binding个数为2,其处理逻辑为:
★ 如果Binding类型为jvm,则设置candidate为JvmBinding。
★ 如果Binding类型为其它类型的Binding,如RestBinding,则调用createProxy(reference,binding)方法,根据Binding类型创建代理对象。
★ 设置JvmBinding的备选代理对象(backupProxy)为刚才创建的其它类型Binding的代理对象。
- 如果candidate不为null,则调用createProxy(reference,candidate)方法,为候选Binding(candidate)创建代理对象proxy。否则,proxy为null。
- 创建DefaultImplementation实例,并设置ReferenceComponent实例target属性为刚才为candidate创建的代理对象proxy。
上述逻辑稍微有点复杂,简单来说就是:
- 在优先使用JVM服务的前提下,如果配置了一个其它类型的服务,如Rest服务,则把它作为JVM服务的备选服务。当JVM服务不可用时,使用备选服务。如果没有配置其它类型服务,则JVM服务没有备选服务,当JVM服务不可用时,则服务不可用,调用失败。
- 在不优先使用JVM服务的前提下,则只能使用配置的其它类型的服务,如Rest服务。
在此案例中,设置candidate为JvmBinding。由于只设置了JvmBinding,没有设置其它类型的Binding,所以JvmBinding的备选代理对象为null。这样当JvmBinding不可用时,无备选方案可用。
在此,我们主要关注一下通过createProxy方法为JvmBinding创建代理对象的过程:
1. private Object createProxy(Referencereference, Binding binding) {
2. BindingAdapter<Binding>bindingAdapter = bindingAdapterFactory.getBindingAdapter(binding
3. .getBindingType());
4. ……略
5. Object proxy;
6. try {
7. proxy = bindingAdapter.inBinding(reference,binding, sofaRuntimeContext);
8. } finally {
9. ……略
10. return proxy;
11. }
根据Binding类型,通过BindingAdapterFactoryImpl类getBindingAdapter方法,获取指定Binding类型的Binding适配器,此处是JvmBindingAdapter。
调用JvmBindingAdapter类inBinding方法创建代理对象:
1. public Object inBinding(Object contract,JvmBinding binding,
2. SofaRuntimeContextsofaRuntimeContext) {
3. return createServiceProxy((Contract)contract, binding, sofaRuntimeContext);
4. }
调用createServiceProxy方法:
1. private Object createServiceProxy(Contractcontract, JvmBinding binding,
2. SofaRuntimeContextsofaRuntimeContext) {
3. ClassLoader newClassLoader;
4. ClassLoader appClassLoader =sofaRuntimeContext.getAppClassLoader();
5. Class<?> javaClass =contract.getInterfaceType();
6.
7. try {
8. Class appLoadedClass =appClassLoader.loadClass(javaClass.getName());
9.
10. if (appLoadedClass == javaClass) {
11. newClassLoader =appClassLoader;
12. } else {
13. newClassLoader =javaClass.getClassLoader();
14. }
15. } catch (ClassNotFoundException e) {
16. newClassLoader =javaClass.getClassLoader();
17. }
18.
19. ClassLoader oldClassLoader =Thread.currentThread().getContextClassLoader();
20.
21. try {
22. Thread.currentThread().setContextClassLoader(newClassLoader);
23. ServiceProxy handler = newJvmServiceInvoker(contract, binding, sofaRuntimeContext);
24. ProxyFactory factory = newProxyFactory();
25. if (javaClass.isInterface()) {
26. factory.addInterface(javaClass);
27. } else {
28. factory.setTargetClass(javaClass);
29. factory.setProxyTargetClass(true);
30. }
31. factory.addAdvice(handler);
32. return factory.getProxy();
33. } finally {
34. Thread.currentThread().setContextClassLoader(oldClassLoader);
35. }
36. }
在createServiceProxy方法中,创建服务代理的主要步骤如下:
- 从sofaRuntimeContext获取应用类加载器appClassLoader,此处为:sun.misc.Launcher$AppClassLoader实例;
- 设置javaClass为入参contract的getInterfaceType方法返回的接口类型,此处为com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService;
- 使用应用类加载器appClassLoader加载服务接口PersonService;
- 设置newClassLoader为javaClass对应的Class对象的类加载器;
- 设置oldClassLoader为当前线程的上下文类加载器;
- 设置当前线程的上下文类加载器为newClassLoader;
- 设置handler,此处为ServiceProxy抽象类的子类JvmServiceInvoker的实例。该类继了ServiceProxy,而ServiceProxy实现了org.aopalliance.intercept.MethodInterceptor接口,表示此处创建AOP代理的方法拦截器。该AOP代理拦截器拦截对proxyClass指定的接口(例如:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService)的方法调用,具体流程在后面详述;
- 创建AOP代理工厂类org.springframework.aop.framework.ProxyFactory的实例factory;
- 配置factory:如果javaClass为接口,则factory的interfaces列表中增加javaClass。否则,设置factory的targetClass为javaClass,proxyTarget为true;
- 把指定的AOP advice实例handler增加到advice链尾部;
- 调用factory的getProxy方法,为指定服务创建AOP代理对象。由于目标类是接口,则使用JDK动态代理技术。Spring使用JDK来生成代理对象,具体的生成代码放在org.springframework.aop.framework.JdkDynamicAopProxy这个类中。JdkDynamicAopProxy 同时实现了AopProxy和InvocationHandler接口,InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。invoke主流程可以简述为:获取可以应用到此方法上的通知链(InterceptorChain),如果有,则应用通知,并执行joinpoint;如果通知链为空,则直接反射执行joinpoint。
关于AOP代理对象的更多细节,在此不详细描述,有兴趣的同学自己在网上查资料看看。
至此,JvmBinding的AOP代理对象创建完毕,此代理对象的handler为JdkDynamicAopProxy,实现了PersonService接口。
接下来,创建DefaultImplementation实例,并设置ReferenceComponent实例target属性为刚才创建的candidate代理对象。
设置ReferenceFactoryBean的proxy为刚才创建的AOP代理对象。这样,当其它类引用某个服务的ReferenceFactoryBean类时,由于此类为工厂Bean,所以通过getObject方法获取的实际对象为AOP代理对象。这样对生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。
至此,JvmBinding类型的服务的代理对象创建完成。
1.1.3 客户端调用
当我们在SpringXML文件中使用sofa:reference引用服务以后,就可以在其它类中引用创建的指定接口的代理对象,并像调用本地Java类那样,调用接口的某个方法。
以下通过在Spring应用上下文中按照名字personReferenceJvm查找com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService接口的代理对象,然后调用其sayName方法,描述客户端调用Jvm服务的流程:
1. public static void main(String[] args)throws InterruptedException {
2.
3. ConfigurableApplicationContext ac =SpringApplication.run(SofaBootRpcDemoApplication.class, args);
4.
5. PersonService personService = (PersonService)ac.getBean("personReferenceJvm");
6.
7. personService.sayName("Mike");
8.
9. }
通过ConfigurableApplicationContext类getBean方法,获取名字为personReferenceJvm的实例,此时返回PersonService接口的AOP代理对象。此代理对象的handler为JdkDynamicAopProxy,实现了PersonService接口。
1. public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable {
2. MethodInvocationinvocation;
3. ObjectoldProxy = null;
4. booleansetProxyContext = false;
5.
6. TargetSourcetargetSource = this.advised.targetSource;
7. Class<?>targetClass = null;
8. Objecttarget = null;
9.
10. try{
11. if(!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
12. //The target does not implement the equals(Object) method itself.
13. returnequals(args[0]);
14. }
15. elseif (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
16. //The target does not implement the hashCode() method itself.
17. returnhashCode();
18. }
19. elseif (method.getDeclaringClass() == DecoratingProxy.class) {
20. //There is only getDecoratedClass() declared -> dispatch to proxy config.
21. returnAopProxyUtils.ultimateTargetClass(this.advised);
22. }
23. elseif (!this.advised.opaque && method.getDeclaringClass().isInterface()&&
24. method.getDeclaringClass().isAssignableFrom(Advised.class)){
25. //Service invocations on ProxyConfig with the proxy config...
26. returnAopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
27. }
28.
29. ObjectretVal;
30.
31. if(this.advised.exposeProxy) {
32. //Make invocation available if necessary.
33. oldProxy= AopContext.setCurrentProxy(proxy);
34. setProxyContext= true;
35. }
36.
37. //May be null. Get as late as possible to minimize the time we "own"the target,
38. //in case it comes from a pool.
39. target= targetSource.getTarget();
40. if(target != null) {
41. targetClass= target.getClass();
42. }
43.
44. //Get the interception chain for this method.
45. List<Object> chain =this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
46.
47. //Check whether we have any advice. If we don't, we can fallback on direct
48. //reflective invocation of the target, and avoid creating a MethodInvocation.
49. if(chain.isEmpty()) {
50. //We can skip creating a MethodInvocation: just invoke the target directly
51. //Note that the final invoker must be an InvokerInterceptor so we know it does
52. //nothing but a reflective operation on the target, and no hot swapping or fancyproxying.
53. Object[]argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
54. retVal= AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
55. }
56. else{
57. //We need to create a method invocation...
58. invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
59. //Proceed to the joinpoint through the interceptor chain.
60. retVal = invocation.proceed();
61. }
62.
63. //Massage return value if necessary.
64. Class<?>returnType = method.getReturnType();
65. if(retVal != null && retVal == target &&returnType.isInstance(proxy) &&
66. !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())){
67. //Special case: it returned "this" and the return type of the method
68. //is type-compatible. Note that we can't help if the target sets
69. //a reference to itself in another returned object.
70. retVal= proxy;
71. }
72. elseif (retVal == null && returnType != Void.TYPE &&returnType.isPrimitive()) {
73. thrownew AopInvocationException(
74. "Nullreturn value from advice does not match primitive return type for: " +method);
75. }
76. returnretVal;
77. }
78. finally{
79. if(target != null && !targetSource.isStatic()) {
80. //Must have come from TargetSource.
81. targetSource.releaseTarget(target);
82. }
83. if(setProxyContext) {
84. //Restore old proxy.
85. AopContext.setCurrentProxy(oldProxy);
86. }
87. }
88. }
获取PersonService接口sayName方法拦截器链,此处只有一个,即[com.alipay.sofa.runtime.service.binding.JvmBindingAdapter$JvmServiceInvoker@4d45094f]。
为sayName方法创建一个ReflectiveMethodInvocation实例invocation,表示一个方法调用。
调用ReflectiveMethodInvocation的proceed方法,执行拦截器链:
1. public Object proceed() throws Throwable {
2. // We start with an index of -1 and incrementearly.
3. if(this.currentInterceptorIndex ==this.interceptorsAndDynamicMethodMatchers.size() - 1) {
4. returninvokeJoinpoint();
5. }
6. ObjectinterceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
7. if(interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
8. ……略
9. }
10. else{
11. //It's an interceptor, so we just invoke it: The pointcut will have
12. //been evaluated statically before this object was constructed.
13. return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
14. }
15. }
从interceptorsAndDynamicMethodMatchers中获取指定位置的拦截器,此处为com.alipay.sofa.runtime.service.binding.JvmBindingAdapter$JvmServiceInvoker的一个实例。
调用JvmBindingAdapter$JvmServiceInvoker的invoke方法:
1. public Object doInvoke(MethodInvocationinvocation) throws Throwable {
2. if (binding.isDestroyed()) {
3. throw newIllegalStateException(……略);
4. }
5. ……略
6. Object retVal;
7. Object targetObj = this.getTarget();
8.
9. if ((targetObj == null ||((targetObj instanceof Proxy) && binding.hasBackupProxy()))) {
10. targetObj =binding.getBackupProxy();
11. ……略
12. }
13.
14. if (targetObj == null) {
15. throw newIllegalStateException(……略);
16. }
17.
18. ClassLoader tcl = Thread.currentThread().getContextClassLoader();
19. try {
20. pushThreadContextClassLoader(sofaRuntimeContext.getAppClassLoader());
21. retVal = invocation.getMethod().invoke(targetObj,invocation.getArguments());
22. } catch (InvocationTargetExceptionex) {
23. throw ex.getTargetException();
24. } finally {
25. ……略
26. popThreadContextClassLoader(tcl);
27. }
28.
29. return retVal;
30. }
调用JvmBindingAdapter$JvmServiceInvoker的getTarget方法,获取目标类:
1. protected Object getTarget() {
2. if (this.target == null) {
3. ComponentName componentName =ComponentNameFactory.createComponentName(
4. ServiceComponent.SERVICE_COMPONENT_TYPE,getInterfaceName(),
5. contract.getUniqueId());
6. ComponentInfo componentInfo =sofaRuntimeContext.getComponentManager()
7. .getComponentInfo(componentName);
8.
9. if (componentInfo != null) {
10. this.target =componentInfo.getImplementation().getTarget();
11. }
12. }
13.
14. return target;
15. }
根据服务接口类型为服务组件创建ComponentName实例componentName,此处组件名的字符串值为service:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService;
从sofaRuntimeContext实例获取组件管理器,然后调用组件管理器getComponentInfo方法,获取名为componentName的服务组件,即ServiceComponent实例;
调用ServiceComponent类getImplementation方法,获取服务组件的属性implementation的值,即DefaultImplementation实例;
最后,调用DefaultImplementation的getTarget方法,获取服务具体实现类实例,此处为PersonServiceImpl的实例。此值是在ServiceFactoryBean初始化时设置的。
获取到target以后,判断target是否为null。如果为null,则设置target为候选代理对象(在一个接口发布多种类型的服务时,如Jvm、Rest等,则会为Jvm类型的服务设置一个候选,以便在Jvm服务不可用时,使用备选服务)。此处不为null,则继续服务调用。
最后,通过Java反射机制,调用目标对象的某个方法,此处为PersonServiceImpl类sayName方法。到此为止,Jvm服务调用完成,返回结果。
1.1.4 服务端响应
由于JVM服务调用是同一个JVM内Java本地方法调用,所以直接执行目标方法,并返回处理结果。