Skip to the content.

Tars接口文件

Tars协议是一种基于 IDL 实现的协议,与 Protocol Buffer 类似,它与语言无关,是一种类 C++ 标识符的语言,用于生成具体的服务接口文件。同时,作为一种二进制协议,相较于常见的 JSON 等文本协议,它的编解码效率更高、网络包占用空间更小。

Tars文件使用.tars作为扩展名,对于.tars文件中的每个服务,生成代码时都会对应产生一个Java接口,若为服务端接口代码生成时会加上Servant后缀,若为客户端接口则会加上Prx后缀。Tars语言的语法规则请参考Tars协议

Tars插件

Tars的maven插件需要在pom.xml文件中添加相关依赖:

<!--tars2java插件-->
<plugin>
	<groupId>com.tencent.tars</groupId>
	<artifactId>tars-maven-plugin</artifactId>
	<version>1.7.0</version>
	<configuration>
		<tars2JavaConfig>
			<!-- tars文件位置 -->
			<tarsFiles>
				<tarsFile>${basedir}/src/main/resources/hello.tars</tarsFile>
			</tarsFiles>
			<!-- 源文件编码 -->
			<tarsFileCharset>UTF-8</tarsFileCharset>
			<!-- 生成服务端代码 -->
			<servant>true</servant>
			<!-- 生成源代码编码 -->
			<charset>UTF-8</charset>
			<!-- 生成的源代码目录 -->
			<srcPath>${basedir}/src/main/java</srcPath>
			<!-- 生成源代码包前缀 -->
			<packagePrefixName>com.qq.tars.quickstart.server.</packagePrefixName>
		</tars2JavaConfig>
	</configuration>
</plugin>

其中的一些配置项如下:

服务端Tars文件

以tars-quick-start中服务端的hello.tars为例:

module TestApp
{
	interface Hello
	{
	    string hello(int no, string name);
	};
};

其中TestApp为名称空间,所有的struct,interface必须在名字空间中。

服务端代码生成时需要设置pom.xml文件中的tars-maven-plugin依赖的servant项为true,之后在工程的根目录下执行mvn tars:tars2java命令即可获得Servant为后缀的接口文件,HelloServant.java:

@Servant
public interface HelloServant {

	public String hello(int no, String name);
}

之后,根据业务逻辑来实现接口即可。

客户端Tars文件

在客户端进行服务调用时,首先需要获取服务端的tars文件,之后在客户端代码生成时需要设置pom.xml文件中的tars-maven-plugin依赖的servant项为false,之后在工程的根目录下执行mvn tars:tars2java命令即可获得Prx为后缀的接口文件,HelloPrx.java:

@Servant
public interface HelloPrx {

	 String hello(int no, String name);

	CompletableFuture<String>  promise_hello(int no, String name);

	 String hello(int no, String name, @TarsContext java.util.Map<String, String> ctx);

	 void async_hello(@TarsCallback HelloPrxCallback callback, int no, String name);

	 void async_hello(@TarsCallback HelloPrxCallback callback, int no, String name, @TarsContext java.util.Map<String, String> ctx);
}

接口提供了三种调用方式:

同步调用

阻塞式的调用方式,客户端会一直阻塞至服务端结果返回。

异步调用

在客户端代码生成时还生成了HelloPrxCallback.java,这是一个普通异步回调处理的抽象类:

public abstract class HelloPrxCallback extends TarsAbstractCallback {

	public abstract void callback_hello(String ret);

}

该类继承了TarsAbstractCallback这个抽象类:

@TarsCallback(comment = "Callback")
public abstract class TarsAbstractCallback implements com.qq.tars.net.client.Callback<TarsServantResponse> {

    @Override
    public final void onCompleted(TarsServantResponse response) {
        TarsServantRequest request = response.getRequest();
        try {
            Method callback = getCallbackMethod("callback_".concat(request.getFunctionName()));
            callback.setAccessible(true);
            callback.invoke(this, ((TarsCodec) response.getSession().getProtocolFactory().getDecoder()).decodeCallbackArgs(response));
        } catch (Throwable ex) {
            throw new ClientException(ex);
        }
    }

    private Method getCallbackMethod(String methodName) throws NoSuchMethodException {
        Method[] methods = getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (methodName.equals(method.getName())) {
                return method;
            }
        }
        throw new NoSuchMethodException("no such method " + methodName);
    }

    @Override
    public final void onException(Throwable ex) {
        callback_exception(ex);
    }

    @Override
    public final void onExpired() {
        callback_expired();
    }

    public abstract void callback_exception(Throwable ex);

    public abstract void callback_expired();
}

通过实现callback_exception,callback_expired和callback_hello方法,可以执行相应的回调逻辑。

promise调用

异步的promise调用方式是Tars v1.7.0新增的功能,该方法返回了一个CompletableFuture对象。CompletableFuture是jdk1.8中新加入的类,它实现了Future<T>, CompletionStage<T>两个接口,提供了十分强大的异步编程功能。在jdk1.8之前主要采用Future或注册CallBack函数来完成异步编程,这两种方式均存在一定的缺陷。Future在调用get方法获取结果时,若操作尚未完成会一直进行等待,此时可能造成CPU时间的浪费。并且Future无法组合完成链式调用,不能为上一个Future获得的结果执行更进一步的操作。而CallBack方式随着回调函数的不断嵌套,则会造成回调金字塔的现象。因此,在Tars v1.7.0版本中引入了CompletableFuture,可以通过注册触发器以回调的方式执行一系列的后续操作。

CompletableFuture的API一般会有带Async后缀和不带Async后缀两种形式,不带Async后缀的方法会由当前的调用线程来执行任务,而带Async后缀的方法则分为两种情况,若在参数中传入了Executor,则会从传入的线程池获取一个线程去执行任务,否则从全局的 ForkJoinPool.commonPool()中获取一个线程中执行这些任务。