Skip to content

Hprose 客户端

小马哥 edited this page Jun 27, 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

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 就可以进行异步调用了,而且可以泛型表明实际的返回值类型,非常方便。

Clone this wiki locally