Skip to content

Hprose 客户端

小马哥 edited this page Jun 28, 2016 · 28 revisions

概述

Hprose 2.0 for Java 支持两种底层网络协议绑定的客户端:HTTP 客户端 和 TCP 客户端。

其中 HTTP 客户端支持跟 HTTP、HTTPS 绑定的 Hprose 服务器通讯。

TCP 客户端支持跟 TCP 绑定的 Hprose 服务器通讯,并且支持全双工和半双工两种模式。

尽管支持这两种不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 Hprose 客户端的功能时,若未涉及到底层网络协议的区别,就以 HTTP 客户端为例来进行说明。

创建客户端

创建客户端有两种方式,一种是直接使用构造器方法,另一种是使用工厂方法 create

使用构造器方法创建客户端

HproseClient 是一个抽象类,因此它不能作为构造器直接使用。如果你想创建一个具体的底层网络协议绑定的客户端,你可以将它作为父类,至于如何实现一个具体的底层网络协议绑定的客户端,这已经超出了本手册的内容范围,这里不做具体介绍,有兴趣的读者可以参考 HproseHttpClientHproseTcpClient 这两个底层网络协议绑定客户端的实现源码。

HproseHttpClient 是 Hprose 的 HTTP 客户端。HproseTcpClient 是 Hprose 的 TCP 客户端。

public HproseHttpClient();
public HproseHttpClient(String uri);
public HproseHttpClient(HproseMode mode);
public HproseHttpClient(String uri, HproseMode mode);
public HproseHttpClient(String[] uris);
public HproseHttpClient(String[] uris, HproseMode mode);

HproseTcpClient 构造器参数跟上面的 HproseHttpClient 参数相同,这里就不在单独列出了。

使用无参构造器创建的客户端,在进行调用前,需要先调用 useService 方法初始化服务器地址。

uriuris 参数是服务器的地址,可以填写一个,也可以填写一组。当填写一组服务器时,客户端会从这些地址当中随机选择一个作为服务地址。因此需要保证这些地址发布的都是完全相同的服务。另外需要注意,当使用 HproseHttpClient 构造器时,地址必须为 http://https:// 开头的地址。而当使用 HproseTcpClient 构造器时,地址必须为 tcp://, tcp4://tcp6:// 开头的地址。

mode 参数表示在数据传输时,对于对象序列化采用何种方式,默认是采用 MemberMode 进行序列化,通常不需要修改该设置。

通过工厂方法 create 创建客户端

public static HproseClient create(String uri) throws IOException, URISyntaxException;
public static HproseClient create(String uri, HproseMode mode) throws IOException, URISyntaxException;
public static HproseClient create(String[] uris, HproseMode mode) throws IOException, URISyntaxException;

工厂方法可以直接在 HproseClient 类上调用,它会根据服务器地址来确定创建什么类型的客户端。如果你需要针对具体传输协议进行一些相关设置,可以将返回值类型转换为 HproseHttpClientHproseTcpClient 进行操作。

当指定一组服务器地址时,需要保证它们的底层传输协议是相同的,服务也是完全相同的。

客户端属性

id 属性

public Integer getId();

只读属性。该属性表示当前客户端在进行推送订阅时的唯一编号。在没有进行推送订阅或者使用自己指定 id 方式进行推送订阅时,该属性的值为 null。你也可以用 invoke 方法调用远程的 '#' 方法来手动从服务器端获取该 id 的值。

timeout 属性

public final int getTimeout();
public final void setTimeout(int timeout);

该属性默认值为 30000,单位是毫秒(ms)。该设置必须大于 0

该属性表示当前客户端在调用时的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。

你也可以针对某个调用进行单独设置。

failswitch 属性

public final boolean isFailswitch();
public final void setFailswitch(boolean failswitch);

该属性默认值为 false

该属性表示当前客户端在因网络原因调用失败时是否自动切换服务地址。当客户端服务地址仅设置一个时,不管该属性值为何,都不会切换地址。

你也可以针对某个调用进行单独设置。

idempotent 属性

public final boolean isIdempontent();
public final void setIdempontent(boolean idempontent);

该属性默认值为 false

该属性表示调用是否为幂等性调用,幂等性调用表示不论该调用被重复几次,对服务器的影响都是相同的。幂等性调用在因网络原因调用失败时,会自动重试。如果 failswitch 属性同时被设置为 true,并且客户端设置了多个服务地址,在重试时还会自动切换地址。

你也可以针对某个调用进行单独设置。

retry 属性

public final int getRetry();
public final void setRetry(int retry);

该属性默认值为 10

该属性表示幂等性调用在因网络原因调用失败后的重试次数。只有 idempotent 属性为 true 时,该属性才有作用。

你也可以针对某个调用进行单独设置。

byref 属性

public final boolean isByref();
public final void setByref(boolean byref);

该属性默认值为 false

该属性表示调用是否为引用参数传递。当设置为引用参数传递时,服务器端会传回修改后的参数值(即使没有修改也会传回)。因此,当不需要该功能时,设置为 false 会比较节省流量。

你也可以针对某个调用进行单独设置。

simple 属性

public final boolean isSimple();
public final void setSimple(boolean simple);

该属性默认值为 false

该属性表示调用中所传输的数据是否为简单数据。简单数据是指:null、数字(包括整数、长整数、浮点数)、boolean 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、Map 和对象。当该属性设置为 true 时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该属性设置为 true,可能会因为死循环导致堆栈溢出的错误。

另外,对于不包含引用 Map 和对象,设置 simpletrue 可能不会加快速度,反而会减慢。因为默认情况下,hprose 会对 Map 中的重复字符串的键值进行引用处理,这种引用处理可以对序列化起到优化作用。而关闭引用处理,也就关闭了这种优化。

你也可以针对某个调用进行单独设置。

因为不同调用的数据可能差别很大,因此,建议不要修改默认设置,而是针对某个调用进行单独设置。

filter 属性

public final HproseFilter getFilter();
public final void setFilter(HproseFilter filter);

该属性默认值为 null。

该属性的作用是可以设置一个 Filter 对象。关于 Filter 对象,我们将作为单独的章节进行介绍,这里暂且略过。

因为通过该属性只能设置一个 Filter 当你需要设置多个 Filter 时,应使用 addFilter 方法代替该属性。

onError 字段

当客户端调用发生错误时,如果没有为调用设置异常处理回调,则该字段将被回调。回调方法有两个参数,第一个参数是方法名(字符串类型),第二个参数是调用中发生的异常。

客户端方法

addFilter 方法

public final void addFilter(HproseFilter filter);

该方法同设置 filter 属性类似。该方法用于添加一个 filter 对象到 Filter 链的末尾,并可以连续添加多个 filter

removeFilter 方法

public final boolean removeFilter(HproseFilter filter);

该方法同设置 filter 属性类似。该方法用于从 Filter 链中删除指定的 filter 对象。

useService 方法

public final void useService(String uri);
public final void useService(String[] uris);
public final <T> T useService(Class<T> type);
public final <T> T useService(Class<T> type, String ns);
public final <T> T useService(String uri, Class<T> type);
public final <T> T useService(String uri, Class<T> type, String ns);
public final <T> T useService(String[] uris, Class<T> type);
public final <T> T useService(String[] uris, Class<T> type, String ns);

该方法有两个用处:

  1. 设置服务器地址
  2. 获取远程服务代理对象

当参数中包含有服务地址 uriuris 时,会设置服务器地址。

当参数中包含有远程服务代理接口类型时,会返回远程服务代理对象。另外,当获取远程服务代理对象时,还可以同时设置该对象的名称空间(ns 参数),当设置了该参数时,每次发起远程调用,都会自动在调用时,添加 ns + "_" 这样的前缀。例如:

假设有这样一个典型的增删改查的接口:

public interface IUserAccessor {
    long add(String name, int age, boolean male, Date birthday);
    boolean delete(long userid);
    void update(long userid, String name, int age, boolean male, Date birthday);
    User fetch(long userid);
}

现在我们假设远程服务器上也定义了一组这样的方法,但是方法名分别是:user_add, user_delete, user_updateuser_fetch,那么我们可以这样来获取一个远程服务代理对象:

IUserAccessor userAccessor = client.useService(IUserAccessor.class, "user");

这样当我们后面要调用方法:

userAccessor.add("张三", 18, true, new SimpleDateFormat("yyyy-MM-dd").parse("1998-05-23"));

会自动转为调用服务器端的 user_add 方法。

Hprose 不同于 RMI,远程服务代理接口可以只在客户端定义(当然在客户端也不是必须的),而不必在服务器端定义,即使两端都有定义,也不需要定义的完全相同,而且通过上面的例子我们可以看出,客户端在接口中定义的方法名也可以跟服务器端的发布的方法名不同。

这大大增加了客户端和服务器端的独立性,让客户端和服务器接口的单独修改成为了可能,这是其它 RPC 技术所不具备的。

invoke 方法

    void invoke(String name, HproseCallback1<?> callback);
    void invoke(String name, HproseCallback1<?> callback, HproseErrorEvent errorEvent);

    void invoke(String name, HproseCallback1<?> callback, InvokeSettings settings);
    void invoke(String name, HproseCallback1<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings);

    void invoke(String name, Object[] args, HproseCallback1<?> callback);
    void invoke(String name, Object[] args, HproseCallback1<?> callback, HproseErrorEvent errorEvent);

    void invoke(String name, Object[] args, HproseCallback1<?> callback, InvokeSettings settings);
    void invoke(String name, Object[] args, HproseCallback1<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings);

    <T> void invoke(String name, HproseCallback1<T> callback, Class<T> returnType);
    <T> void invoke(String name, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType);

    <T> void invoke(String name, HproseCallback1<T> callback, Class<T> returnType, InvokeSettings settings);
    <T> void invoke(String name, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings);

    <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, Class<T> returnType);
    <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType);

    <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, Class<T> returnType, InvokeSettings settings);
    <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings);

    void invoke(String name, Object[] args, HproseCallback<?> callback);
    void invoke(String name, Object[] args, HproseCallback<?> callback, HproseErrorEvent errorEvent);
    void invoke(String name, Object[] args, HproseCallback<?> callback, InvokeSettings settings);
    void invoke(String name, Object[] args, HproseCallback<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings);

    <T> void invoke(String name, Object[] args, HproseCallback<T> callback, Class<T> returnType);
    <T> void invoke(String name, Object[] args, HproseCallback<T> callback, HproseErrorEvent errorEvent, Class<T> returnType);
    <T> void invoke(String name, Object[] args, HproseCallback<T> callback, Class<T> returnType, InvokeSettings settings);
    <T> void invoke(String name, Object[] args, HproseCallback<T> callback, HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings);

    Object invoke(String name) throws Throwable;
    Object invoke(String name, InvokeSettings settings) throws Throwable;

    Object invoke(String name, Object[] args) throws Throwable;
    Object invoke(String name, Object[] args, InvokeSettings settings) throws Throwable;

    <T> T invoke(String name, Class<T> returnType) throws Throwable;
    <T> T invoke(String name, Class<T> returnType, InvokeSettings settings) throws Throwable;

    <T> T invoke(String name, Object[] args, Class<T> returnType) throws Throwable;
    <T> T invoke(String name, Object[] args, Class<T> returnType, InvokeSettings settings) throws Throwable;

该方法是客户端的最核心方法,它的功能就是进行远程调用。

name 参数是远程方法名。

args 参数是远程调用的参数。

returnType 参数是返回结果类型,比如要返回字符串结果可以写:String.class

callback 参数表示异步调用的回调方法。将调用成功时回调。

errorEvent 参数表示异步调用的异常处理回调方法,将在调用失败时回调,如果不指定该参数,将默认执行 client 上的 onError 回调。

settings 参数表示调用选项,其中包含以下一些属性:

  • mode
  • async
  • byref
  • simple
  • failswitch
  • timeout
  • idempotent
  • retry
  • oneway
  • returnType
  • userData

mode 属性

public HproseResultMode getMode();
public void setMode(HproseResultMode mode);

该设置表示结果返回的类型,它有4个取值,分别是:

  • HproseResultMode.Normal
  • HproseResultMode.Serialized
  • HproseResultMode.Raw
  • HproseResultMode.RawWithEndTag

HproseResultMode.Normal 是默认值,表示返回正常的已被反序列化的结果。

HproseResultMode.Serialized 表示返回的结果保持序列化的格式。

HproseResultMode.Raw 表示返回原始数据。

HproseResultMode.RawWithEndTag 表示返回带有结束标记的原始数据。

这样说明也许有些晦涩,让我们来看一个例子就清楚了:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.HproseResultMode;
import hprose.common.InvokeSettings;
import hprose.util.StrUtil;
import java.nio.ByteBuffer;

public class Exam1 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        InvokeSettings settings = new InvokeSettings();
        settings.setMode(HproseResultMode.Normal);
        System.out.println(
            client.invoke(
                "Hello",
                new Object[] { "World" },
                String.class,
                settings
            )
        );
        settings.setMode(HproseResultMode.Serialized);
        System.out.println(
            StrUtil.toString(
                client.invoke(
                    "Hello",
                    new Object[] { "World" },
                    ByteBuffer.class,
                    settings
                )
            )
        );
        settings.setMode(HproseResultMode.Raw);
        System.out.println(
            StrUtil.toString(
                client.invoke(
                    "Hello",
                    new Object[] { "World" },
                    ByteBuffer.class,
                    settings
                )
            )
        );
        settings.setMode(HproseResultMode.RawWithEndTag);
        System.out.println(
            StrUtil.toString(
                client.invoke(
                    "Hello",
                    new Object[] { "World" },
                    ByteBuffer.class,
                    settings
                )
            )
        );
    }
}

该程序执行结果如下:

Hello World
s11"Hello World"
Rs11"Hello World"
Rs11"Hello World"z

除了直接用 Invoke 调用,还可以使用代理接口方式调用,例如上面的例子可以改写为:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.HproseResultMode;
import hprose.common.MethodName;
import hprose.common.ResultMode;
import hprose.io.ByteBufferStream;
import hprose.util.StrUtil;
import java.nio.ByteBuffer;

interface IExam2 {
    String hello(String name);
    @MethodName("hello")
    @ResultMode(HproseResultMode.Serialized)
    byte[] hello2(String name);
    @MethodName("hello")
    @ResultMode(HproseResultMode.Raw)
    ByteBuffer hello3(String name);
    @MethodName("hello")
    @ResultMode(HproseResultMode.RawWithEndTag)
    ByteBufferStream hello4(String name);
}

public class Exam2 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam2 exam = client.useService(IExam2.class);
        System.out.println(exam.hello("World"));
        System.out.println(StrUtil.toString(exam.hello2("World")));
        System.out.println(StrUtil.toString(exam.hello3("World")));
        System.out.println(StrUtil.toString(exam.hello4("World")));
    }
}

我们会发现,用接口方式调用远程方法,要简单方便的多。而且我们可以为同一个远程方法定义多个不同的本地方法,然后用 @MethodName 注解来指定实际的远程方法名,mode 属性也不需要用 InvokeSettings 来设置,只需要用 @ResultMode 注解来设置就可以了。返回值类型也可以直接声明,我们会发现,Normal 以外的三种模式,返回值除了可以是 ByteBuffer 类型,还可以指定为 byte[]ByteBufferStream 类型,在实际应用中,你可以根据自己的需要来定义你需要的返回值类型。

async 属性

public boolean isAsync();
public void setAsync(boolean async);

当设置该属性为 true 时,invoke 的返回结果为 Promise 对象。

例如:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.InvokeSettings;
import hprose.util.concurrent.Promise;

public class Exam3 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        InvokeSettings settings = new InvokeSettings();
        settings.setAsync(true);
        Promise<String> result = (Promise<String>)client.invoke("hello", new Object[] { "World" }, settings);
        result.then((String value) -> System.out.println(value));
        Thread.sleep(1000);
    }
}

运行结果为:

Hello World

因为是异步调用,所以后面加了 Thread.sleep(1000),否则结果还未输出,程序就结束了。

直接通过 invoke 设置 async 属性进行异步调用不太方便,如果使用接口方式的话,会方便很多。例如:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.util.concurrent.Promise;

interface IExam4 {
    Promise<String> hello(String name);
}
public class Exam4 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam4 exam = client.useService(IExam4.class);
        exam.hello("World").then((String value) -> System.out.println(value));
        Thread.sleep(1000);
    }
}

接口方式下,直接将返回值定义成 Promise<T> 就可以进行异步调用了,而且可以泛型表明实际的返回值类型,非常方便。

byref 属性

public boolean isByref();
public void setByref(boolean byRef);

该设置表示调用是否为引用参数传递方式。例如:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.InvokeSettings;
import java.util.HashMap;
import java.util.Map;

public class Exam5 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        InvokeSettings settings = new InvokeSettings();
        settings.setByref(true);
        Map<String, String> map = new HashMap<>();
        map.put("January", "Jan");
        map.put("February", "Feb");
        map.put("March", "Mar");
        map.put("April", "Apr");
        map.put("May", "May");
        map.put("June", "Jun");
        map.put("July", "Jul");
        map.put("August", "Aug");
        map.put("September", "Sep");
        map.put("October", "Oct");
        map.put("November", "Nov");
        map.put("December", "Dec");
        Object[] arguments = new Object[] {map};
        client.invoke("swapKeyAndValue", arguments, settings);
        System.out.println(map);
        System.out.println(arguments[0]);
    }
}

注意,因为 Java 本身并不支持引用参数传递,因此,这个调用并不会实际修改 map 变量的值,而是修改的 arguments 的参数值。

运行结果如下:

{June=Jun, October=Oct, December=Dec, May=May, September=Sep, March=Mar, July=Jul, January=Jan, February=Feb, April=Apr, August=Aug, November=Nov}
{Jul=July, Oct=October, Jun=June, Feb=February, Apr=April, Aug=August, Dec=December, May=May, Nov=November, Jan=January, Sep=September, Mar=March}

这个用接口方式的话,只能用于异步调用,而不能用于同步调用,原因跟上面说的相同,下面是接口方式引用参数传递异步调用的例子:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.ByRef;
import hprose.common.HproseCallback;
import java.util.HashMap;
import java.util.Map;

interface IExam6 {
    @ByRef
    void swapKeyAndValue(Map<String, String> map, HproseCallback<Map<String, String>> callback);
}

public class Exam6 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam6 exam = client.useService(IExam6.class);
        Map<String, String> map = new HashMap<>();
        map.put("January", "Jan");
        map.put("February", "Feb");
        map.put("March", "Mar");
        map.put("April", "Apr");
        map.put("May", "May");
        map.put("June", "Jun");
        map.put("July", "Jul");
        map.put("August", "Aug");
        map.put("September", "Sep");
        map.put("October", "Oct");
        map.put("November", "Nov");
        map.put("December", "Dec");
        exam.swapKeyAndValue(map, (Map<String, String> value, Object[] a) ->
        {
            System.out.println(map);
            System.out.println(a[0]);
        });
        Thread.sleep(1000);
    }
}

simple 属性

public boolean isSimple();
public void setSimple(boolean simple);

该设置表示本次调用中所传输的参数是否为简单数据。前面在属性介绍中已经进行了说明,这里就不在重复。这里只举一个接口调用方式的例子:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.common.SimpleMode;

interface IExam7 {
    @SimpleMode
    String hello(String name);
}

public class Exam7 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam7 exam = client.useService(IExam7.class);
        System.out.println(exam.hello("World"));
    }
}

在这里 @SimpleMode 注解不管加不加,运行结果都是一样的,加上速度可能会稍快一点。

failswitch 属性

public boolean isFailswitch();
public void setFailswitch(boolean failswitch);

该设置表示当前调用在因网络原因失败时是否自动切换服务地址。

当使用接口调用时,可以在接口定义中用 @Failswitch 来标记方法。

timeout 属性

public int getTimeout();
public void setTimeout(int timeout);

该设置表示本次调用的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。

当使用接口调用时,可以在接口定义中用 @Timeout 来标记方法。

idempotent 属性

public boolean isIdempotent();
public void setIdempotent(boolean idempotent);

该设置表示本次调用是否为幂等性调用,幂等性调用在因网络原因调用失败时,会自动重试。

当使用接口调用时,可以在接口定义中用 @Idempotent 来标记方法。

retry 属性

public int getRetry();
public void setRetry(int retry);

该设置表示幂等性调用在因网络原因调用失败后的重试次数。只有 idempotent 设置为 true 时,该设置才有作用。

当使用接口调用时,可以在接口定义中用 @Retry 来标记方法。

oneway 属性

public boolean isOneway();
public void setOneway(boolean oneway);

该设置表示当前调用是否不等待返回值。当该设置为 true 时,请求发送之后,并不等待服务器返回结果而立即往下执行。

当使用接口调用时,可以在接口定义中用 @Oneway 来标记方法,oneway 调用的返回值类型应设置为 void

returnType 属性

public Type getReturnType();
public void setReturnType(Type type);

该设置表示返回值的类型,这里使用 Type 类型而不是 Class 类型,因此可以接受泛型类型设置。

当设置为异步调用时,该类型的值为 Promise 类型中的泛型参数的值,而不需要加上 Promise 类型。

通过接口方式调用远程方法,直接在接口的方法定义中定义返回值类型就可以了,要比直接使用 invoke 方法加该设置要方便的多。

通过上面的介绍,我们可以看出,通过接口方式调用可以实现直接使用 invoke 调用的全部功能,而且更加方便易用,因此,推荐使用接口方式来调用远程方法。invoke 方式通常仅用于调试阶段。

userData 属性

public Map<String, Object> getUserData();
public void setUserData(Map<String, Object> userdata);

该属性是一个 Map<String, Object> 类型的对象,它用于存放一些用户自定义的数据。这些数据可以通过 context 对象在整个调用过程中进行传递。当你需要实现一些特殊功能的 FilterHandler 时,可能会用到它。

异步链式调用

Hprose 2.0 for Java 新增了返回 Promise 对象的异步调用方式,因此它可以进行链式调用,例如:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.util.concurrent.Promise;

interface IExam8 {
    Promise<Integer> sum(int a, int b);
}

public class Exam8 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam8 exam = client.useService(IExam8.class);
        exam.sum(1, 2)
            .then((Integer result) -> {
                return exam.sum(result, 3);
            })
            .then((Integer result) -> {
                return exam.sum(result, 4);
            })
            .then((Integer result) -> System.out.println(result));
        Thread.sleep(1000);
    }
}

输出结果为:

10

更简单的异步顺序调用

Hprose 2.0 的远程调用的参数本身也可以是 PromiseFuture 对象。

因此,上面的链式调用还可以直接简化为:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.util.concurrent.Promise;

interface IExam9 {
    Promise<Integer> sum(int a, int b);
    Promise<Integer> sum(Promise<Integer> a, int b);
    Promise<Integer> sum(int a, Promise<Integer> b);
    Promise<Integer> sum(Promise<Integer> a, Promise<Integer> b);
}

public class Exam9 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam9 exam = client.useService(IExam9.class);
        exam.sum(exam.sum(exam.sum(1, 2), 3), 4)
            .then((Integer result) -> System.out.println(result));
        Thread.sleep(1000);
    }
}

这比上面的链式调用更加直观。

注意,在异步调用中,尽量不要使用 Future 类型的参数,因为 Future 类型的参数在获取值时是同步的,因此可能会造成阻塞。而 Promise 类型的参数不会有这个问题。

当一个调用的参数依赖于其它几个调用的结果时候,这种方式的优越性会更加明显,例如:

package hprose.example.client;

import hprose.client.HproseClient;
import hprose.util.concurrent.Promise;
import java.util.Arrays;

interface IExam10 {
    Promise<Integer> sum(int a, int b);
    Promise<Integer> sum(Promise<Integer> a, int b);
    Promise<Integer> sum(int a, Promise<Integer> b);
    Promise<Integer> sum(Promise<Integer> a, Promise<Integer> b);
}

public class Exam10 {
    public static void main(String[] args) throws Throwable {
        HproseClient client = HproseClient.create("http://www.hprose.com/example/");
        IExam10 exam = client.useService(IExam10.class);
        Promise<Integer> r1 = exam.sum(1, 3);
        Promise<Integer> r2 = exam.sum(2, 4);
        Promise<Integer> r3 = exam.sum(r1, r2);
        Promise.run(Integer.class, (Integer[] r) -> {
            System.out.println(Arrays.toString(r));
        }, r1, r2, r3);
        Thread.sleep(1000);
    }
}

这个程序的运行结果为:

[4, 6, 10]

该程序虽然是异步执行,但是书写方式却是几乎是同步的。

而且这里还有一个好处,r1r2 两个调用的参数之间没有依赖关系,是两个相互独立的调用,因此它们将会并行执行,而 r3 的调用依赖于 r1r2,因此 r3 会等 r1r2 都执行结束后才会执行。也就是说,它不但保证了有依赖关系的调用会根据依赖关系顺序执行,而且对于没有依赖的调用还能保证并行执行。

这是回调方式和链式调用方式都很难做到的,即使可以做到,也会让代码变得晦涩难懂。

这也是 Hprose 2.0 最大的改进之一。

subscribe 方法

public final void subscribe(String name, Action<Object> callback) throws Throwable;
public final void subscribe(String name, Action<Object> callback, int timeout) throws Throwable;
public final <T> void subscribe(String name, Action<T> callback, Type type) throws Throwable;
public final <T> void subscribe(String name, Action<T> callback, Type type, int timeout) throws Throwable;

public final void subscribe(String name, Integer id, Action<Object> callback);
public final void subscribe(String name, Integer id, Action<Object> callback, int timeout);
public final <T> void subscribe(String name, Integer id, Action<T> callback, Type type);
public final <T> void subscribe(String name, Integer id, Action<T> callback, Type type, int timeout);

subscribe 方法的用处是订阅服务器端的推送服务。该方法有两种方式,一种是自动获取设置客户端 id,另一种是手动设置客户端 id

参数 name 是订阅的主题名,它实际上也是一个服务器端的方法,该方法与普通方法的区别是,它只有一个参数 id,该参数表示客户端的唯一编号,该方法的返回值即推送信息,当返回值为 null 或者抛出异常时,客户端会忽略并再次调用该 name 对应的方法。当该方法返回推送消息时,callback 回调函数会执行,并同时再次调用该 name 对应的方法。因此当没有推送消息时,该方法不应该立即返回值,而应该挂起等待,直到超时或者有推送消息时再返回结果。

当然,对于开发者来说,自己实现一个完善的推送方法还是有一定难度的。因此,Hprose 2.0 的服务器端已经提供了一整套的专门用于推送的 API,通过这些 API,可以方便的自动实现用于推送的服务方法。在后面介绍服务器端时,我们再介绍这部分内容。

参数 id 是客户端的唯一编号,如果省略的话,客户端会使用自动编号机制,如果该自动编号未初始化,会自动调用一个名字为 # 的服务器端远程方法,之所以使用这个特殊的名字是为了防止跟用户发布的普通方法发生冲突。Hprose 2.0 服务器已经自动实现了该方法,但是用户也可以用自己的实现来替换它,它的默认实现是一个整数自增计数器。当用户指定了 id 参数时,客户端会将它作为该 name 对应方法的参数值传给服务器端,但不会修改客户端的 id 属性值。

参数 callback 是用来处理推送消息的回调函数,该参数不能省略。

参数 type 是服务器端推送数据的类型,跟回调参数的类型一致,这里使用 Type 而不是 Class 是为了支持泛型。

参数 timeout 是等待推送消息的超时时间,单位是毫秒(ms),可以省略,默认值与 timeout 属性值相同。超时之后并不会产生异常,而是重新请求推送。因此,如果用户要在服务器端自己实现推送方法,应当注意处理好同一个客户端对同一个推送方法可能会进行重复调用的问题。如果使用 Hprose 2.0 提供的推送 API,则不需要关心这一点。

对于同一个推送主题,subscribe 方法允许被多次调用,这样可以对同一个推送主题指定多个不同的回调方法。但通常没有必要也不推荐这样做。

unsubscribe 方法

public void unsubscribe(String name)
public <T> void unsubscribe(String name, Action<T> callback)
public void unsubscribe(String name, Integer id)
public <T> void unsubscribe(String name, Integer id, Action<T> callback)

该方法用于取消订阅推送主题。当调用该方法时,带有 callback 参数,将只取消对该 callback 方法的回调,除非这是该主题上最后一个 callback,否则对该主题远程方法的调用并不会中断。当所有的 callback 都被取消之后,或者当调用该方法时,没有指定 callback 参数时,将会中断对该主题远程方法的循环调用。

如果 id 参数未指定,那么当客户端 id 属性有值时,将只取消对该 id 属性值对应的推送主题的订阅。当客户端 id 属性未初始化时,将会取消该主题上所有的订阅。

通常来说,当你调用 subscribe 方法时如果指定了 id 参数,那么当调用 unsubscribe 方法时你也应该指定相同的 id 参数。当你调用 subscribe 方法时没有指定 id 参数,那么当调用 unsubscribe 方法时你也不需要指定 id 参数。

Clone this wiki locally