Ark 服务通信

为了解决 Biz 之间的通信问题,SOFAArk 引入了 SOFABoot 提供的SofaService/SofaReference 编程界面,本文介绍它的使用方法。

说明

如果要解决 Plugin 和 Biz 的通信问题,可发布和引用插件服务。操作方式,请参见 Ark 服务机制

引入依赖

引入 runtime-sofa-boot-plugin 依赖:

<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>runtime-sofa-boot-plugin</artifactId>
    <version>${sofa.boot.version}</version>
</dependency>
  • 如果应用基于 Spring Boot 1.x 开发,推荐使用 V2.6.1 版本。

  • 如果应用基于 Spring Boot 2.x 开发,推荐使用 V3.1.3 版本。

发布和引用 JVM 服务

SOFAArk 引入了 SOFABoot 提供的SofaService/SofaReferenceJVM 服务概念,SOFABoot 提供以下三种方式给开发人员发布和引用 JVM 服务:

  • XML 方式

  • Annotation 方式

  • 编程 API 方式

说明

更多信息,请参见 发布和引用 JVM 服务

XML 方式

服务发布

首先需要定义一个 Bean:

<bean id="sampleService" class="com.alipay.sofa.runtime.test.service.SampleServiceImpl">

然后通过 SOFA 提供的 Spring 扩展标签来将上面的 Bean 发布成一个 SOFA JVM 服务。

<sofa:service interface="com.alipay.sofa.runtime.test.service.SampleService" ref="sampleService">
     <sofa:binding.jvm/>
</sofa:service>
  • interface是需要发布成服务的接口。

  • ref指向需要发布成 JVM 服务的 Bean。

服务引用

使用 SOFA 提供的 Spring 扩展标签引用服务:

<sofa:reference interface="com.alipay.sofa.runtime.test.service.SampleService" id="sampleServiceRef">
    <sofa:binding.jvm/>
</sofa:service>
  • interface是服务的接口,需要和发布服务时配置的interface一致。

  • id属性的含义同 Spring BeanId。

以上配置会生成一个 id 为sampleServiceRef的 Spring Bean,您可以将sampleServiceRef这个 Bean 注入到当前 SOFABoot 模块 Spring 上下文的任意地方。

说明

service/reference标签还支持 RPC 服务发布,详情请参见 发布 SOFARPC 服务

Annotation 方式

除了通过 XML 方式发布 JVM 服务和引用之外,SOFABoot 还提供了 Annotation 的方式来发布和引用 JVM 服务。

发布服务

通过 Annotation 方式发布 JVM 服务,只需要在实现类上加一个@SofaService注解即可。示例如下:

@SofaService
public class SampleImpl implements SampleInterface{
    public void test(){

    }
}

注意事项

  • @SofaService的作用是将一个 Bean 发布成一个 JVM 服务,虽然您可以不用再写<sofa:service/\>配置,但是还是需要事先将@SofaService所注解的类配置成一个 Spring Bean。

  • 如果一个服务已经被加上了@SofaService的注解,就不能再以 XML 的方式发布服务。

  • 当被@SofaService注解的类只有一个接口时,框架会直接采用这个接口作为服务的接口;当被 @SofaService注解的类实现了多个接口时,可以设置@SofaServiceinterfaceType字段来指定服务接口,例如:

    @SofaService(interfaceType=SampleInterface.class)
    public class SampleImpl implements SampleInterface,Serializable{
         public void test(){
    
         }
    }
  • 使用@SofaService注解发布服务时,需要在实现类上打上@SofaService注解。在 Spring Boot 使用 Bean Method 创建 Bean 时,会导致@Bean@SofaService分散在两处,而且无法对同一个实现类使用不同的 uniqueid。因此,自 SOFABoot v3.1.0 版本起,支持@SofaService作用在 Bean Method 之上,例如:

    @Configuration
    public class SampleSofaServiceConfiguration{
       @Bean("sampleSofaService")
       @SofaService(uniqueId ="service1")
        SampleService service(){
           return new SampleServiceImpl("");
        }
    }

引用服务

@SofaService对应,SOFA 提供了@SofaReference来引用一个 JVM 服务。假设我们需要在一个 Spring Bean 中使用SampleJvmService 这个 JVM 服务,只需要在字段上加上一个@SofaReference 的注解即可:

public class SampleServiceRef{
    @SofaReference
     private SampleService sampleService;
}

注意事项

  • @SofaService类似,在@SofaReference上也没有指定服务接口,因为@SofaReference在不指定服务接口时,会采用被注解字段的类型作为服务接口。您也可以通过设定@SofaReferenceinterfaceType属性来指定接口:

    public class SampleServiceRef{
       @SofaReference(interfaceType=SampleService.class)
       private SampleService sampleService;
    }
  • 为了方便在 Spring Boot Bean Method 使用注解@SofaReference引用服务,自 SOFABoot v2.6.0(开源版)及 v3.1.0(商业版)版本起,支持在 Bean Method 参数上使用@SofaReference注解引用 JVM 服务,例如:

    @Configuration
    public class MultiSofaReferenceConfiguration{
         @Bean("sampleReference")
         TestService service(@Value("$spring.application.name")String appName,
         @SofaReference(uniqueId ="service")SampleService service){
                 return new TestService(service);
         }
    }

编程 API 方式

SOFABoot 为 JVM 服务的发布和引用提供了一套编程 API 方式,方便直接在代码中发布和引用 JVM 服务。与 Spring 的ApplicationContextAware类似,若使用编程 API 方式,首先需要实现ClientFactoryAware接口获取编程组件 API:

public class ClientFactoryBean implements ClientFactoryAware{
        private ClientFactory clientFactory;

        @Override
        public void setClientFactory(ClientFactory clientFactory){
            this.clientFactory = clientFactory;
        }
    }

发布服务

SampleService 为例,使用clientFactory通过编程 API 方式发布 JVM 服务的代码如下:

ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);

ServiceParam serviceParam =newServiceParam();
serviceParam.setInstance(newSampleServiceImpl());
serviceParam.setInterfaceType(SampleService.class);
serviceClient.service(serviceParam);

流程如下:

  1. 通过clientFactory获得ServiceClient对象。

  2. 构造ServiceParam对象。

    ServiceParam对象包含发布服务所需参数,通过setInstance方法设置需要被发布成 JVM 服务的对象,通过setInterfaceType设置服务的接口。

  3. 调用ServiceClientservice方法发布一个 JVM 服务。

引用服务

通过编程 API 方式引用 JVM 服务的代码如下:

ReferenceClient referenceClient = clientFactory.getClient(ReferenceClient.class);

ReferenceParam<SampleService> referenceParam =new ReferenceParam<SampleService>();
referenceParam.setInterfaceType(SampleService.class);
SampleService proxy = referenceClient.reference(referenceParam);

流程如下:

  1. ClientFactory中获取一个ReferenceClient

  2. 和发布一个服务类似,构造出一个 ReferenceParam,然后设置好服务的接口。

  3. 调用ReferenceClientreference方法。

重要

通过动态客户端创建的 Reference 对象是一个非常重要的对象,请您在使用时不要频繁创建,并自行做好缓存,否则可能存在内存溢出的风险。

除了实现ClientFactoryAware接口用于获取ServiceClientReferenceClient对象,您还可以使用简便的注解@SofaClientFactory获取编程 API,例如:

public class ClientBean{
   @SofaClientFactory
   private ReferenceClient referenceClient;

   @SofaClientFactory
   private ServiceClient serviceClient;
}

Unique ID

某些时候,针对一个接口,可能会需要发布两个服务,分别对应到不同的实现。以之前的SampleService为例,可能有两个SampleService的实现,这两个实现都需要发布成 SOFA 的 JVM Service。按照上面的教程采用 XML 的方式,就可能用下面这种方式进行配置:

<sofa:service interface="com.alipay.sofa.runtime.test.service.SampleService" ref="sampleService1">
</sofa:service>
<sofa:service interface="com.alipay.sofa.runtime.test.service.SampleService "ref="sampleService2">
</sofa:service>

配置完成后,服务发布没有什么问题,但是当需要引用服务的时候,就会出现问题。例如以下配置中,我们无法得知这个 JVM 引用到底引用的是哪个 JVM 服务:

<sofa:reference interface="com.alipay.sofa.runtime.test.service.SampleService" id="sampleService">
</sofa:reference>

为了解决以上问题,SOFABoot 引入了 Unique ID 的概念,针对服务接口一样的 JVM 服务,您可以通过 Unique ID 来进行区分。例如在上面服务发布的代码加入 Unique ID:

<sofa:service interface="com.alipay.sofa.runtime.test.service.SampleService" ref="sampleService1" unique-id="ss1">
</sofa:service>
<sofa:service interface="com.alipay.sofa.runtime.test.service.SampleService" ref="sampleService2" unique-id="ss2">
</sofa:service>

然后,在引用服务时指定 Unique ID。例如要使用 sampleService1 的服务,可以指定unique-id为 ss1:

<sofa:reference interface="com.alipay.sofa.runtime.test.service.SampleService" id="sampleService" unique-id="ss1">
</sofa:reference>

要使用 sampleService2 的服务,可以指定unique-id为 ss2:

<sofa:reference interface="com.alipay.sofa.runtime.test.service.SampleService" id="sampleService" unique-id="ss2">
</sofa:reference>
说明

以上示例展示的是在 XML 的方式中使用 Unique ID,当您用 Annotation 的方式发布 JVM 服务和引用的时候,可以通过设置@SofaServiceSofaReference的 Unique ID 属性来设置 Unique ID;当您用编程 API 的方式发布或者引用 JVM 服务的时候,可以通过ServiceParamReferenceParamsetUniqueId方法来设置 Unique ID。

跳过序列化

在 Biz 之间使用 JVM 服务调用时,因为每个 Biz 有单独的类加载器加载,因此每次 JVM 调用都会走 Hessian 序列化协议。某些情况下,为了提升性能,您可能不希望使用序列化,而是使用直接调用的方式。此时需要做两步额外的工作,以下面接口服务为例:

public interface SampleService{
     Result service();
}
  1. 打包插件。

    因为使用直接调用的方式,因此接口类SampleService及其依赖(比如参数、返回值等)都需要下沉为 Ark Plugin,并在插件配置中将这些类导出。这样做的目的是多个 Biz 中使用接口时,该接口类统一委托给 Plugin 加载,否则报错如下:

    java.lang.IllegalArgumentException:objectisnot an instance of declaring class
      at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:498)
      at com.alipay.sofa.runtime.integration.invoke.DynamicJvmServiceProxyFinder$DynamicJvmServiceInvoker.doInvoke(DynamicJvmServiceProxyFinder.java:164)
      at com.alipay.sofa.runtime.spi.service.ServiceProxy.invoke(ServiceProxy.java:39)
      at com.alipay.sofa.runtime.service.binding.JvmBindingAdapter$JvmServiceInvoker.doInvoke(JvmBindingAdapter.java:171)
      at com.alipay.sofa.runtime.spi.service.ServiceProxy.invoke(ServiceProxy.java:39)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
      at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
      at com.sun.proxy.$Proxy73.service(UnknownSource)
      at com.alipay.sofa.demo.service.SampleRestService.sampleService(SampleRestService.java:87)
  2. 发布服务。

    通过注解或者 XML 方式发布 JVM 服务,在跨 Biz 调用时,都会走序列化。如果您想跳过,需要在发布服务时指定serialize = false,操作方式如下:

    • 注解指定

      @SofaService(bindings ={@SofaServiceBinding(serialize =false)})
    • XML 指定

      <sofa:binding.jvmserialize="true"/>