SOFABoot源码解析之JVM服务

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本地方法调用,所以直接执行目标方法,并返回处理结果。

猜你喜欢

转载自blog.csdn.net/beyondself_77/article/details/80849273