Java远程方法调用
Java远程方法调用(Remote Method Invocation,RMI)是Java的一个核心API和类库,允许一个Java虚拟机上运行的Java程序调用不同虚拟机上运行的对象中的方法,即使这两个虚拟机运行于物理隔离的不同主机上。在某种程度上,RMI可以看成RPC的Java升级版。
Java RMI的开发往往从远程接口的定义开始,本例使用的远程接口如下:
import java.rmi.Remote;
import java.rmi.RemoteException;
//远程接口必须声明为public
//远程接口必须继承自java.rmi.Remote
public interface RMIQueryStatus extends Remote {
//远程接口中的函数必须将java.rmi.RemoteException声明于其throws子句内
RMIFileStatus getFileStatus(String filename)
throws RemoteException;
}
远程接口中函数的实现和普通的成员函数并没有太大的区别(除了它可能抛出java.rmi.RemoteException异常以外)。相关代码如下:
//RMIQueryStatusImpl继承自UnicastRemoteObject,并实现了RMIQueryStatus接口
public class RMIQueryStatusImpl
extends UnicastRemoteObject implements RMIQueryStatus {
……
@Override
public RMIFileStatus getFileStatus(String filename) throws ……{
RMIFileStatus status=new RMIFileStatus(filename);
……
return status;
}
……
}
RMI远端对象注册点rmiregistry是Java自带的一个工具,该应用程序位于Java运行环境的bin目录下。通过下面的命令启动Java RMI注册服务(使用默认端口):
rmiregistry
然后启动服务器RMIQueryStatusServer,该服务器的main方法先创建RMIQueryStatusImpl对象,并与刚才启动的对象注册点对话,将远端对象绑定到名字“rmi://yourserver.com:12090/query”上(应用这个例子的时候,请将这个URI的主机部分改为相应的主机名)。代码如下:
public static void main(String[] args) {
try {
//创建RMIQueryStatusImpl对象
RMIQueryStatusImpl queryService=new RMIQueryStatusImpl();
LocateRegistry.createRegistry(12090);
//绑定远端对象到名字
Naming.rebind(RMI_URL,queryService);
System.out.println("Server ready");
} catch(Exception e) {
e.printStackTrace();
}
}
客户端RMIQueryStatusClient从远端对象注册点中,通过Naming.lookup()获取远程引用。一旦获取对象引用,并恢复了对象类型,客户端就可以使用此引用,调用远程对象的远程方法,即getFileStatus()方法。相关代码如下:
try {
//创建RMIQueryStatusImpl对象
RMIQueryStatus query=
(RMIQueryStatus)Naming.lookup(RMIQueryStatusServer.RMI_URL);
//调用远程方法;该调用如同调用本地方法
RMIFileStatus status=query.getFileStatus("/tmp/testRMI");
System.out.println(status);
} catch(RemoteException e) {
//远程方法和普通方法的一个差别:远程方法可能抛出RemoteException异常
e.printStackTrace();
}
Java动态代理
Java动态代理类位于java.lang.reflect包下,主要包括java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
java.lang.reflect.Proxy中比较重要的方法有:
public class Proxy implements java.io.Serializable {
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>...interfaces)
……
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
……
public static boolean isProxyClass(Class<?> cl)
……
public static InvocationHandler getInvocationHandler(Object proxy)
……
}
其中,Proxy的静态方法getProxyClass()用于获得代理类的java.lang.Class对象。
Proxy类还提供以下方法:
isProxyClass():判断某java.lang.Class对象是否是代理类。
getInvocationHandler():获取代理实例对应的调用处理程序(即构造代理实例时传入的InvocationHandler实例)。
调用InvocationHandler实例的invoke()方法。相关代码如下:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable;
}
传递给invoke()方法的3个参数分别是:
proxy:代理对象本身。
method:用户调用的代理对象上的方法。
args:传递给该方法的参数。
动态代理实例
public interface PDQueryStatus {
DPFileStatus getFileStatus(String filename);
}
public class DPInvocationHandler implements InvocationHandler {
private DPQueryStatusImpl dpqs;//目标对象
//构造函数,传入目标对象
public DPInvocationHandler(DPQueryStatusImpl dpqs) {
this.dpqs=dpqs;
}
@Override
public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable {
Object ret=null;
//实现了附加的功能,往控制台输出调用参数的String表示
String msg1=MessageFormat.format("Calling method {0}({1})",
method.getName(),
Arrays.toString(args));
System.out.println(msg1);
//调用转发
ret=method.invoke(dpqs,args);
//其他附加功能
……
return ret;
}
}
静态方法create()用于创建一个到DPQueryStatusImpl实例(也就是目标对象)的代理对象。相关代码如下:
public static PDQueryStatus create(DPQueryStatusImpl dpqs) {
//调用newProxyInstance需要的Class<?>[]参数
Class<?>[] interfaces=new Class[] { PDQueryStatus.class };
//调用newProxyInstance需要的DPInvocationHandler参数
DPInvocationHandler handler=new DPInvocationHandler(dpqs);
Object result
=Proxy.newProxyInstance(dpqs.getClass().getClassLoader(),
interfaces,handler);
……
return(PDQueryStatus) result;
}
最终调用DPQueryStatusImpl的同名方法。相关代码如下:
public static void main(String[] args) {
try {
PDQueryStatus query=DPMain.create(new DPQueryStatusImpl());
DPFileStatus status=query.getFileStatus("/tmp/testDP");
System.out.println(status);
} catch(Exception e) {
e.printStackTrace();
}
}
Hadoop中的远程过程调用
Hadoop IPC接口必须继承自org.apache.hadoop.ipc.VersionedProtocol接口,代码如下:
public interface VersionedProtocol {
//返回协议接口的版本
//输入参数是协议接口的类名和客户端的版本号,输出是服务器的协议接口版本号
public long getProtocolVersion(String protocol,
long clientVersion) throws IOException;
}
IPCQueryStatus接口定义如下:
IPC接口必须继承自VersionedProtocol
public interface IPCQueryStatus extends VersionedProtocol {
IPCFileStatus getFileStatus(String filename);
}
Hadoop中IPC接口的实现中的方法与普通方法没有什么差别。代码如下:
public class IPCQueryStatusImpl implements IPCQueryStatus {
……
@Override
public IPCFileStatus getFileStatus(String filename) {
IPCFileStatus status=new IPCFileStatus(filename);
……
return status;
}
@Override
public long getProtocolVersion(String protocol,long clientVersion)
throws IOException {
System.out.println("protocol:"+protocol);
System.out.println("clientVersion:"+clientVersion);
return IPCQueryServer.IPC_VER;
}
}
和Java RMI相比,Hadoop IPC建立服务器的方法非常简单,通过org.hadoopinternal.ipc.IPC的静态方法getServer(),就可以在一个IPCQueryStatusImpl实例上,获得一个提供IPCQueryStatus接口功能的IPC服务器。一旦获得服务器实例,就可以通过它的start()方法和stop()方法启动和停止服务器。具体代码如下:
public class IPCQueryServer {
public static final int IPC_PORT=32121;
public static final long IPC_VER=5473L;
public static void main(String[] args) {
try {
IPCQueryStatusImpl queryService=new IPCQueryStatusImpl();
//在对象queryService上获得一个IPC服务器
Server server=RPC.getServer(queryService,
"0.0.0.0",IPC_PORT,new Configuration());
server.start();//服务器开始服务
……
server.stop();//停止服务器
……
} catch(Exception e) {
e.printStackTrace();
}
}
}
访问IPC服务器的客户端代码如下。
public class IPCQueryClient {
public static void main(String[] args) {
try {
//为RPC.getProxy准备Socket地址
InetSocketAddress addr=new InetSocketAddress("localhost",
IPCQueryServer.IPC_PORT);
//通过创建IPC客户端,获得接口IPCQueryStatus的一个实例
IPCQueryStatus query=(IPCQueryStatus)
RPC.getProxy(IPCQueryStatus.class,//接口的类对象
IPCQueryServer.IPC_VER,//接口版本
addr,//服务器地址
new Configuration());
//调用远程方法;该调用如同调用本地方法
IPCFileStatus status=query.getFileStatus("/tmp/testIPC");
System.out.println(status);
//停止客户端
RPC.stopProxy(query);
} catch(Exception e) {
e.printStackTrace();
}
}
}
Hadoop IPC的代码结构
共7个文件,包括远程过程调用实现需要的辅助类:
RemoteException:远程异常,应用于IPC客户端,表示远程过程调用中的错误。
Status.java:Status是一个枚举类,定义了远程过程调用的返回结果,包括SUCCESS(成功)、ERROR(一般错误)和FATAL(致命错误)三种情况。
VersionedProtocol接口:前面已经介绍过,Hadoop IPC的远程接口都扩展自VersionedProtocol。
ConnectionHeader.java:IPC客户端与服务器建立连接时发送的消息头。
与客户端、服务器实现相关的代码主要在Client.java、Server.java和RPC.java三个文件中。1)Client.java包含了与IPC客户端相关的代码,如图4-11所示。Client是对IPC客户的抽象,它包含的内部类可以分为与IPC连接相关的,如Client.Connection和Client.ConnectionId等;和远程调用Call相关的如Client.Call、Client.ParallelCall等(后面会详细介绍IPC连接、远程调用Call的相关概念)。
2)Server.java实现了一个IPC服务器端的抽象类。如图4-12所示。
3)RPC.java在Client.java和Server.java基础上实现了Hadoop IPC。如图4-13所示。
RPC.java中实现的功能主要分为两部分:与客户端相关的功能包括RPC.ClientCache、RPC.Invoker和RPC.Invocation,其中RPC.Invoker继承自前面介绍的调用句柄java.lang.reflect.InvocationHandler。与服务器相关的内部类只有RPC.Server,它是IPC服务器抽象类(定义在Server.java中)的一个具体子类。
Client.Connection的成员变量
private InetSocketAddress server;//IPC服务器地址
private ConnectionHeader header;//连接消息头
private ConnectionId remoteId;//IPC连接标识
private Socket socket=null;//TCP连接的Socket对象
private DataInputStream in;
private DataOutputStream out;
//当前正在处理的远程调用
private Hashtable<Integer,Call> calls=new Hashtable<Integer,Call>();
//IPC连接的最后一次通信时间
private AtomicLong lastActivity=new AtomicLong();
//连接关闭标记
private AtomicBoolean shouldCloseConnection=new AtomicBoolean();
private IOException closeException;//导致IPC连接关闭的异常
Server.Connection的成员变量
private boolean rpcHeaderRead=false;//状态,是否已经读入RPC版本号
private boolean headerRead=false;//状态,是否已经读入连接消息头
private SocketChannel channel;
private ByteBuffer data;
private ByteBuffer dataLengthBuffer;
private LinkedList<Call> responseQueue;
private volatile int rpcCount=0;//当前正在处理的RPC请求量
private long lastContact;
private int dataLength;
private Socket socket;
//客户端的主机名和端口
private String hostAddress;
private int remotePort;
ConnectionHeader header=new ConnectionHeader();
Class<?> protocol;
Subject user=null;
//一个假的远程调用,作为鉴权失败时的应答
//(Fake 'call' for failed authorization response)
private final int AUTHROIZATION_FAILED_CALLID=-1;
private final Call authFailedCall=
new Call(AUTHROIZATION_FAILED_CALLID,null,null);
private ByteArrayOutputStream authFailedResponse=
new ByteArrayOutputStream();
建立IPC连接
连接是在需要的时候,也就是有IPC调用发生的时候才建立的。相关代码如下:
//org.apache.hadoop.ipc.Client的代码片段
private Hashtable<ConnectionId,Connection> connections=
new Hashtable<ConnectionId,Connection>();
……
private Connection getConnection(InetSocketAddress addr,
Class<?> protocol,
UserGroupInformation ticket,
Call call)
throws IOException {
……
do {
synchronized(connections) {
connection=connections.get(remoteId);
if(connection==null) {
connection=new Connection(remoteId);
connections.put(remoteId,connection);
}
}
} while(!connection.addCall(call));
……
connection.setupIOstreams();
}
/org.apache.hadoop.ipc.Client.Connection的代码片段
private class Connection extends Thread {
……
public Connection(ConnectionId remoteId) throws IOException {
……
}
……
private synchronized void setupIOstreams() {
if(socket !=null||shouldCloseConnection.get()) {
return;
}
……
writeHeader();
//更新连接维护时间
touch();
//启动接受线程
start();
……
}
……
private synchronized boolean addCall(Call call) {
if(shouldCloseConnection.get())
return false;
calls.put(call.id,call);
notify();
return true;
}
……
}
Listener.run()实现了NIO中的选择器循环,即调用选择器的select()方法并处理事件,它的处理过程和前面介绍的NIO实例有点差别,这里只通过doRead()方法处理OP_READ,以及通过doAccept()方法处理OP_ACCEPT事件,不关注通道的OP_WRITE事件。
public void run() {
……
while(running) {
SelectionKey key=null;
try {
selector.select();//下面是典型的选择器代码
Iterator<SelectionKey> iter=selector.selectedKeys().iterator();
while(iter.hasNext()) {
key=iter.next();
iter.remove();
try {
if(key.isValid()) {
if (key.isAcceptable())
doAccept(key);//处理acctpt
else if(key.isReadable())
doRead(key);//处理read
}
} catch(IOException e) {
}//catch里什么事情都没做
key=null;
}//while(iter.hasNext())
} catch(OutOfMemoryError e) {
……//应对没有内存的情况
closeCurrentConnection(key,e);
cleanupConnections(true);//后面讨论关闭连接时再介绍
try { Thread.sleep(60000); } catch(Exception ie) {}
} catch(InterruptedException e) {
……//其他异常情况
} catch(Exception e) {
closeCurrentConnection(key,e);
}
cleanupConnections(false);
}
……
}
在版本检查阶段,readAndProcess()先读IPC连接魔数和协议版本号,IPC连接魔数一共4字节,正好是缓冲区dataLengthBuffer的长度(这是精心设计的),协议版本号1字节,通过versionBuffer缓冲区读入。
public int readAndProcess() throws IOException,InterruptedException {
……
if(!versionRead) {
……
int version=versionBuffer.get(0);
dataLengthBuffer.flip();
if(!HEADER.equals(dataLengthBuffer)||version !=CURRENT_VERSION)
{
……
return–1;//比较IPC连接魔数和协议版本号失败
}
……
}
……
if(data.remaining()==0) {
……
if(headerRead) {
……
} else {
processHeader();//处理连接头
headerRead=true;
data=null;
try {//对连接进行鉴权
authorize(user,header);
……
} catch(AuthorizationException ae) {
authFailedCall.connection=this;
setupResponse(authFailedResponse,authFailedCall,
Status.FATAL,null,
ae.getClass().getName(),ae.getMessage());
responder.doRespond(authFailedCall);
return–1;//鉴权失败关闭连接
}
}
……
}
}
……
private void processHeader() throws IOException {
DataInputStream in=
new DataInputStream(new ByteArrayInputStream(data.array()));
header.readFields(in);
try {
String protocolClassName=header.getProtocol();
if(protocolClassName !=null) {
protocol=getProtocolClass(header.getProtocol(),conf);
}
} catch(ClassNotFoundException cnfe) {
throw new IOException("Unknown protocol:"+header.getProtocol());
}
……//获取用户信息
}
数据分帧和读写
在IPC客户端中,Connection.sendParam()用于将远程调用Call形成消息发送出去。sendParam()方法首先将要发送的数据写入DataOutputBuffer对象中,以同时获取数据长度和消息内容,然后通过输出流out发送数据。代码如下:
public void sendParam(Call call) {
……
DataOutputBuffer d=null;
try {
synchronized(this.out) {
……
//将远程调用Call串行化到一个DataOutputBuffer中
d=new DataOutputBuffer();
d.writeInt(call.id);
call.param.write(d);
byte[] data=d.getData();
int dataLength=d.getLength();
out.writeInt(dataLength);//输出数据长度
out.write(data,0,dataLength);//输出数据
out.flush();
}
}
……
}
服务器端接收数据在Connection.readAndProcess(),上一节已经分析过这个方法的部分代码。与数据分帧相关的代码如下(代码可以分为两部分:读取“显式长度”和读取消息):
public int readAndProcess() throws IOException,InterruptedException {
while(true) {
//最多读入一个RPC的数据
int count=-1;
if (dataLengthBuffer.remaining() > 0) {
//读入“显式长度”,dataLengthBuffer一共4字节
count=channelRead(channel,dataLengthBuffer);
if (count < 0||dataLengthBuffer.remaining() > 0)
return count;//“显式长度”还没有读完,或者读过程中出错
}
if (!versionRead) {//处理握手过程中的版本信息
……
}
if (data==null) {
dataLengthBuffer.flip();
dataLength=dataLengthBuffer.getInt();//数据长度已读取
if (dataLength==Client.PING_CALL_ID) {//心跳消息
dataLengthBuffer.clear();
return 0;//ping message
}
data=ByteBuffer.allocate(dataLength);//为读取数据分配缓冲区
incRpcCount();//Increment the rpc count
}
count=channelRead(channel,data);
if (data.remaining()==0) {//读到一个完整的消息
dataLengthBuffer.clear();
data.flip();
if (headerRead) {
processData();//处理数据
data=null;
return count;
} else {
……
}
}
return count;
}
}
维护IPC连接
发送心跳消息使用sendPing()方法,代码如下:
private synchronized void sendPing() throws IOException {
long curTime=System.currentTimeMillis();
if ( curTime– lastActivity.get() >=pingInterval) {
lastActivity.set(curTime);
synchronized(out) {
out.writeInt(PING_CALL_ID);//发送–1
out.flush();
}
}
}
PingInputStream继承自FilterInputStream,FilterInputStream提供了在某一特定流的基础上提供附加功能的能力。具体到PingInputStream类,它在原有Socket提供的输入流SocketInputStream上添加心跳检查的能力,代码如下:
private synchronized void setupIOstreams(){
……
this.socket.setSoTimeout(pingInterval);
……
}
private class PingInputStream extends FilterInputStream {
//构造函数,需要一个InputStream
protected PingInputStream(InputStream in) {
super(in);
}
//处理超时,如果客户端处于运行状态,则调用sendPing()方法
private void handleTimeout(SocketTimeoutException e) throws…… {
if (shouldCloseConnection.get()||!running.get()) {
throw e;
} else {
sendPing();
}
}
public int read() throws IOException {
do {
try {
return super.read();
} catch(SocketTimeoutException e) {
handleTimeout(e);//处理SocketTimeoutException异常
}
} while(true);
}
……
}
服务器端的连接维护相对简单,当接收到客户端的数据时,服务器连接也会更新它的成员变量lastContact(如下):
private synchronized void doRead() throws IOException {
……
c.setLastContact(System.currentTimeMillis());
……
}
public void setLastContact(long lastContact) {
this.lastContact=lastContact;
}
关闭IPC连接
IPC连接上长时间没有发生IPC调用的判断在waitForWork()中,代码如下:
//waitForWork()如果返回false,会导致IPC连接关闭
private synchronized boolean waitForWork() {
//注意if语句的判断条件,与的条件包括:1)目前没有正在处理的远程调用;
//2)连接不需要关闭;3)客户端还处于运行状态
if (calls.isEmpty() && !shouldCloseConnection.get() && running.get())
{
long timeout=maxIdleTime-
(System.currentTimeMillis()-lastActivity.get());//等待时间
if (timeout>0) {
try {
//通过wait等待,可能被到达的数据打断,
//也可能被Client.stop()打断或超时
wait(timeout);
} catch(InterruptedException e) {}
}
}
if (!calls.isEmpty() && !shouldCloseConnection.get() && running.get())
{
return true;//需要等待连接上返回的远程调用结果
} else if(shouldCloseConnection.get()) {
return false;//shouldCloseConnection置位,关闭连接
} else if(calls.isEmpty()) {
markClosed(null);
return false;//连接长时间处于空闲状态,关闭
} else {
markClosed((IOException)new IOException().initCause(
new InterruptedException()));
return false;//连接上还有未结束的远程调用,客户端被打断,记录原因
}
}
……
public void run() {
……
while(waitForWork()) {
receiveResponse();//如果连接上还有数据要处理,处理数据
}
close();//关闭连接
……
}
客户端方法调用过程
IPCQueryStatus query=(IPCQueryStatus) RPC.getProxy(IPCQueryStatus.class,
IPCQueryServer.IPC_VER,addr,new Configuration());
IPCFileStatus status=query.getFileStatus("/tmp/testIPC");
org.apache.hadoop.ipc.Client,即RPC客户端,如代码所示,在构造函数中对象通过ClientCache.getClient()方法获得该成员变量。代码如下:
private static class Invoker implements InvocationHandler {
……
private Client client;
……
public Invoker(InetSocketAddress address,UserGroupInformation ticket,
Configuration conf,SocketFactory factory) {
……
this.client=CLIENTS.getClient(conf,factory);
}
public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable {
……
ObjectWritable value=(ObjectWritable) client.call(
new Invocation(method,args),address,
method.getDeclaringClass(),ticket);
……
return value.get();
}
/* 关闭IPC客户端,简单调用ClientCache的静态方法stopClient()*/
synchronized private void close() {
……
}
}
Client.call()方法代码如下:
public Writable call(Writable param,InetSocketAddress addr,
Class<?> protocol,UserGroupInformation ticket)
throws InterruptedException,IOException {
Call call=new Call(param);
Connection connection=getConnection(addr,protocol,ticket,call);
connection.sendParam(call);//在IPC连接上发送调用的相关信息
boolean interrupted=false;
synchronized(call) {
while(!call.done) {
try {
call.wait();//等待结果
} catch(InterruptedException ie) {
interrupted=true;//远程调用被打断
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
if (call.error !=null) {
if (call.error instanceof RemoteException) {
call.error.fillInStackTrace();
throw call.error;//远程调用异常返回,抛异常给本地调用者
} else {//本地处理出现的异常
throw wrapException(addr,call.error);
}
} else {
return call.value;//调用正常结束,返回结果
}
}
Call.callComplete()其他可能的调用均来自Connection.receiveResponse(),如方法名receiveResponse()用于接收服务器发送的调用处理结果。相关代码如下:
private void receiveResponse() {
……
try {
int id=in.readInt();//获取调用ID
……
Call call=calls.remove(id);
int state=in.readInt();//远程调用的处理结果
if (state==Status.SUCCESS.state) {//成功
Writable value=ReflectionUtils.newInstance(valueClass,conf);
value.readFields(in);//读取返回值
call.setValue(value);
} else if(state==Status.ERROR.state) {//异常
call.setException(
new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));
} else if(state==Status.FATAL.state) {//致命错误
markClosed(new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));//关闭连接
}
} catch(IOException e) {
markClosed(e);
}
}
服务器端方法调用过程
Listener主要运行NIO选择器循环,并在Listener.doRead()方法中读取数据,在Connection.readAndProcess()中恢复数据帧,然后调用processData()。
private void processData() throws IOException,InterruptedException {
DataInputStream dis=
new DataInputStream(new ByteArrayInputStream(data.array()));
int id=dis.readInt();//调用标识符
……
Writable param=ReflectionUtils.newInstance(paramClass,conf);
param.readFields(dis);//RPC.Invocation对象
Call call=new Call(id,param,this);
callQueue.put(call);//将调用对象放入队列中
}
抽象方法Server.call()通过Writable返回调用的结果,或者通过IOException告知调用过程中发生了异常。看以下代码:
//抽象的call()方法
public abstract Writable call(Class<?> protocol,
Writable param,long receiveTime)throws IOException;
……
public void run() {
……
ByteArrayOutputStream buf=new ByteArrayOutputStream(10240);
while(running) {
try {
final Call call=callQueue.take();//获取一个远程调用请求
……
String errorClass=null;
String error=null;
Writable value=null;
……
try {
//通过Subject.doAs()调用方法
value=Subject.doAs(call.connection.user,
new PrivilegedExceptionAction<Writable>() {
@Override
public Writable run() throws Exception {
//方法调用
return call(call.connection.protocol,
call.param,call.timestamp);
}
}
);
} catch(PrivilegedActionException pae) {
Exception e=pae.getException();
errorClass=e.getClass().getName();
error=StringUtils.stringifyException(e);
} catch(Throwable e) {
errorClass=e.getClass().getName();
error=StringUtils.stringifyException(e);
}
……
setupResponse(buf,call,
(error==null) ? Status.SUCCESS:Status.ERROR,
value,errorClass,error);
responder.doRespond(call);
} catch(InterruptedException e) {
……
} catch(Exception e) {
……
}
}
……
}