1、Java动态代理实例

Java 动态代理一个简单的demo:(用以对比Hadoop中的动态代理)

Hello接口:

public interface Hello {
void sayHello(String to);
void print(String p);
}

Hello接口的实现类:

public class HelloImpl implements Hello { 
     
   public void sayHello(String to) { 
        System.out.println("Say hello to " + to); 
    } 
     
   public void print(String s) { 
        System.out.println("print : " + s); 
    } 
     
}

与代理类(HelloImpl类)相关联的InvocationHandler对象

public class LogHandler implements InvocationHandler { 
     
   private Object dele; 
     
   public LogHandler(Object obj) { 
       this.dele = obj; 
    } 
     
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        doBefore(); 
       //在这里完全可以把下面这句注释掉,而做一些其它的事情 

        Object result = method.invoke(dele, args); 
        after(); 
       return result; 
    } 
     
   private void doBefore() { 
        System.out.println("before...."); 
    } 
     
   private void after() { 
        System.out.println("after...."); 
    } 
}

最后测试代码如下:

public class ProxyTest { 
 
   public static void main(String[] args) { 
        HelloImpl impl = new HelloImpl(); 
        LogHandler handler= new LogHandler(impl); 
       //这里把handler与impl新生成的代理类相关联 

        Hello hello = (Hello) Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass().getInterfaces(), handler); 
         
       //这里无论访问哪个方法,都是会把请求转发到handler.invoke

        hello.print("All the test"); 
        hello.sayHello("Denny"); 
    } 
 
}

 

2、Hadoop中的动态代理

2.1、客户端方法调用过程

IPC客户端的处理比动态代理实例稍微复杂:代理对象上的调用被InvocationHandler捕获后,请求被打包并通过IPC连接发送到服务器上,客户端等待并在服务器的处理应答到达后,生成并返回调用结果。IPC上的调用是个同步操作,即,线程会一直等待调用结束,才会开始后续处理;而网络的处理时异步的,请求发送后,不需要等待应答。客户端通过java的wait()/notify()机制简单地解决了异步网络处理和同步IPC调用的差异。

 

Hadoop对外提供查询文件状态的接口,如下:

public interface IPCQueryStatus extends VersionedProtocol {
IPCFileStatus getFileStatus(String filename);
}

客户端通过如下代码调用:

IPCQueryStatus query = (IPCQueryStatus) RPC.getProxy(IPCQueryStatus.class, IPCQueryServer.IPC_VER, addr, new Configuration());
IPCFileStatus status = query.getFileStatus("\tmp\testIPC");

2.1.1、Client端动态代理实现

在RPC的getProxy代码如下:

public static VersionedProtocol getProxy(
Class<? extends VersionedProtocol> protocol,
long clientVersion, InetSocketAddress addr, UserGroupInformation ticket,
Configuration conf, SocketFactory factory, int rpcTimeout) throws IOException { ......
VersionedProtocol proxy =
(VersionedProtocol) Proxy.newProxyInstance(
protocol.getClassLoader(), new Class[] { protocol },
new Invoker(protocol, addr, ticket, conf, factory, rpcTimeout));
......
return proxy;
......
}

需要制定一个InvocationHandler,对于所有的调用请求,这个InvocationHandler都是Invoke,如下:

private static class Invoker implements InvocationHandler {
private Client.ConnectionId remoteId;// 用来标示一个connection,用以复用
private Client client;//最重要的成员变量,RPC客户端
private boolean isClosed = false; public Invoker(Class<? extends VersionedProtocol> protocol,
InetSocketAddress address, UserGroupInformation ticket,
Configuration conf, SocketFactory factory,
int rpcTimeout) throws IOException {
this.remoteId = Client.ConnectionId.getConnectionId(address, protocol,
ticket, rpcTimeout, conf);
this.client = CLIENTS.getClient(conf, factory);//★
}
...... public Object invoke(Object proxy, Method method, Object[] args)
...... ObjectWritable value = (ObjectWritable)
client.call(new Invocation(method, args), remoteId);
...... return value.get();
}
}

在上面的代码中,client负责发送IPC请求,并获取结果,类似最上面demo中LogHandler中的dele。

2.1.2、Client通过Connection发送IPC请求并获取结果

如下为client.call方法调用Connection.sendParam发送IPC请求:

public Writable call(Writable param, ConnectionId remoteId)
throws InterruptedException, IOException {
Call call = new Call(param);
Connection connection = getConnection(remoteId, call);
connection.sendParam(call); // send the parameter
...
synchronized (call) {
while (!call.done) {
try {
call.wait(); // wait for the result
} catch (InterruptedException ie) {
...
}
} ...
if (call.error != null) {
...
throw call.error;
...
} else {
return call.value;
}
}
}

connection.sendParam后,会再调用receiveMessage来获取返回结果。如下:

private class Connection extends Thread {
...... public void run() {
......
while (waitForWork()) {//wait here for work - read or close connection
receiveResponse();
}
......
}
......
private void receiveResponse() {
......
touch(); try {
int id = in.readInt(); // try to read an id
......
Call call = calls.get(id); int state = in.readInt(); // read call status
if (state == Status.SUCCESS.state) {
Writable value = ReflectionUtils.newInstance(valueClass, conf);
value.readFields(in); // read value
call.setValue(value);
calls.remove(id);
} else if (state == Status.ERROR.state) {
call.setException(new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));
calls.remove(id);
} else if (state == Status.FATAL.state) {
// Close the connection
markClosed(new RemoteException(WritableUtils.readString(in),
WritableUtils.readString(in)));
}
} catch (IOException e) {
markClosed(e);
}
}
}

connection会调用call的setValue或者setException,两个方法都会调用callComplete方法,来调用notify通知进程IPC调用已结束

protected synchronized void callComplete() {
this.done = true;
notify(); // notify caller
} public synchronized void setException(IOException error) {
this.error = error;
callComplete();
} public synchronized void setValue(Writable value) {
this.value = value;
callComplete();
}

 

2.2、服务器端方法调用过程

服务端由Listener接收。

2.2.1、Listener接收IPC请求的工作过程

Listener主要运行NIO选择器循环,并在Listener.doRead()方法中读取数据,Connection.readAndProcess()中恢复数据帧,然后调用processData().

void Listener.doRead(SelectionKey key) throws InterruptedException {
int count = 0;
Connection c = (Connection)key.attachment();
...
count = c.readAndProcess();
... } public int Connection.readAndProcess() throws IOException, InterruptedException {
......
processOneRpc(data.array());
......
} private void Connection.processOneRpc(byte[] buf) throws IOException,
InterruptedException {
if (headerRead) {
processData(buf);
} else {
processHeader(buf);
......
}
} private void Connection.processData(byte[] buf) throws IOException, InterruptedException {
DataInputStream dis =
new DataInputStream(new ByteArrayInputStream(buf));
int id = dis.readInt(); // try to read an id ......
Writable param = ReflectionUtils.newInstance(paramClass, conf);//★??paramClass在哪儿设置的★在RPC.Server中,paramClass是Invocation,IPC调用传递的都是Invocation
param.readFields(dis); Call call = new Call(id, param, this);
callQueue.put(call); // queue the call; maybe blocked here
}

ProcessData反序列化调用参数,构造服务器端的Call对象。然后放入callQueue队列中。callQueue阻塞队列定义于Server类中,是Listener和Handler的边界。(生产者Listener消费者Handler)。

2.2.2、Handler处理IPC请求的工作过程

Handler主要工作都在run方法中完成。主循环中,每循环一次处理一个请求(通过调用Server的抽象方法call来完成)。

public void run() {
......
SERVER.set(Server.this);
ByteArrayOutputStream buf =
new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
while (running) { final Call call = callQueue.take(); // 获取一个IPC调用
......
String errorClass = null;
String error = null;
Writable value = null; CurCall.set(call);
......
value = call(call.connection.protocol, call.param,
call.timestamp);//实际代码用到jaas,这里简化
...... CurCall.set(null);
synchronized (call.connection.responseQueue) {
......
setupResponse(buf, call,
(error == null) ? Status.SUCCESS : Status.ERROR,
value, errorClass, error);
...
responder.doRespond(call);//★?
} } }

Server.call调用后返回一个writable对象--value,然后通过调用setupResponse将结果序列化到call的Response成员变量中。

private void setupResponse(ByteArrayOutputStream response,
Call call, Status status,
Writable rv, String errorClass, String error)
throws IOException {
response.reset();
DataOutputStream out = new DataOutputStream(response);
out.writeInt(call.id); // write call id
out.writeInt(status.state); // write status if (status == Status.SUCCESS) {
rv.write(out);
} else {
WritableUtils.writeString(out, errorClass);
WritableUtils.writeString(out, error);
}
......
call.setResponse(ByteBuffer.wrap(response.toByteArray()));
}

Server.call抽象方法的具体实现在RPC.Server中。代码如下:

private Object instance;
...... public Writable call(Class<?> protocol, Writable param, long receivedTime)
throws IOException { Invocation call = (Invocation)param; Method method =
protocol.getMethod(call.getMethodName(),
call.getParameterClasses());
method.setAccessible(true); Object value = method.invoke(instance, call.getParameters()); return new ObjectWritable(method.getReturnType(), value); }

Handler所在线程是共享资源,当有一个IPC请求处理完后,即调用Response的doResponse返回结果,而不亲自返回,原因有二:

1. 对共享资源的占用时间越短越好;

2. IPC返回受网络通信时间影响,可能会占用很长时间。

2.2.3、Response的工作过程

doResponse的代码很简单,将Call放入IPC连接的应答队列中,如果应答队列为1,立即调用processResponse发放向客户端发送结果,(队列为1,表明此IPC连接比较空闲,直接发送,避免从Handler线程到Response线程的切换开销)

void doRespond(Call call) throws IOException {
synchronized (call.connection.responseQueue) {
call.connection.responseQueue.addLast(call);
if (call.connection.responseQueue.size() == 1) {
processResponse(call.connection.responseQueue, true);
}
}
}

Response有一个类似于Listener的NIO选择器,用来处理当队列不为1时的发送。只是Listener关注OP_READ和OP_ACCEPT事件,而Response关注OP_WRITE事件。代码如下:

public void run() {

      while (running) {

          waitPending();     // 等待通道登记
writeSelector.select(PURGE_INTERVAL); // 等待通道可写
Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
try {
if (key.isValid() && key.isWritable()) {
doAsyncWrite(key);//输出远程IPC调用结果
}
} catch (IOException e) {
}
}
......
}
} private void doAsyncWrite(SelectionKey key) throws IOException {
Call call = (Call)key.attachment();
......
synchronized(call.connection.responseQueue) {
if (processResponse(call.connection.responseQueue, false)) {//调用输出
try {
key.interestOps(0);//processResponse返回true,表示无等待数据,清楚兴趣操作集
} catch (CancelledKeyException e) {
......
}
}
}
} private boolean processResponse(LinkedList<Call> responseQueue,
boolean inHandler) throws IOException {
......
synchronized (responseQueue) {
......
int numBytes = channelWrite(channel, call.response); done = true; // error. no more data for this channel.
closeConnection(call.connection);
}
return done;
}

processResponse关键点:

1. 可被Handler调用(当应答队列为1),参数inHandler为true,也可被Response调用,参数inHandler为false,表示队列为1或更多。

2. 返回true,表示通道上无需要发送的数据。

2.3总结

IPC Client端,发送Client.Call(new Invocation(method,args), remoteId)

--封装过程:Call.Id ,  Invocation---(查看Client.Connection.sendParam)

IPC Server端,接收Server.Call(Id, Invocation, Connction)---封装过程:Call.Id,Invocation--(查看Server.Connction.processData)

Hadoop中客户端和服务器端的方法调用过程的更多相关文章

  1. JVM方法调用过程

    JVM方法调用过程 重载和重写 同一个类中,如果出现多个名称相同,并且参数类型相同的方法,将无法通过编译.因此,想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同.这种方法上的联系就是重载 ...

  2. http协议中客户端8种请求方法

    http请求中的8种请求方法 1.opions   返回服务器针对特定资源所支持的HTML请求方法   或web服务器发送*测试服务器功能(允许客户端查看服务器性能) 2.Get   向特定资源发出请 ...

  3. 聊聊 C# 中的多态底层 (虚方法调用) 是怎么玩的

    最近在看 C++ 的虚方法调用实现原理,大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针,而 vtable array 中的每一个指针元素指向的就是各自的 ...

  4. TCP三次握手四次挥手过程及各过程中客户端和服务器端的状态。

    #三次握手 客户端向服务器端发送SYN包,客户端进入SYN_SEND状态 服务器端收到客户端发送的包返回ACK+SYN包,服务器端进入SYN_RECV状态 客户端收到服务器端返回的包再发回ACK包,客 ...

  5. go微服务框架go-micro深度学习(四) rpc方法调用过程详解

    上一篇帖子go微服务框架go-micro深度学习(三) Registry服务的注册和发现详细解释了go-micro是如何做服务注册和发现在,服务端注册server信息,client获取server的地 ...

  6. go微服务框架go-micro深度学习 rpc方法调用过程详解

    摘要: 上一篇帖子go微服务框架go-micro深度学习(三) Registry服务的注册和发现详细解释了go-micro是如何做服务注册和发现在,服务端注册server信息,client获取serv ...

  7. mybatis源码分析(方法调用过程)

    十一月月底,宿舍楼失火啦,搞得20多天没有网,目测直到放假也不会来了... 正题 嗯~,其实阅读源码不是为了应付面试,更重要的让你知道,大师是怎样去写代码的,同样是用Java,为啥Clinton Be ...

  8. c++中六种构造函数的实现以及9中情况下,构造函数的调用过程

    六种构造函数的实现代码例如以下: #include<iostream> using namespace std; //c++中六种默认的构造函数 class Test { public: ...

  9. cocos2d-x 2.x版本中,场景切换各方法调用顺序

    假设从A场景切换到B场景,调用各场景方法的顺序为: 如果没有切换效果(transition),则先调用B的init(),再调用A的onExitTransitionStart(),接着调用A的onExi ...

随机推荐

  1. Foreign key (referential) constraints on DB2 LUW v105

    oreign key constraints (also known as referential constraints or referential integrity constraints) ...

  2. IOS 开发,调用打电话,发短信,打开网址

    IOS 开发,调用打电话,发短信,打开网址   1.调用 自带mail [[UIApplication sharedApplication] openURL:[NSURL URLWithString: ...

  3. MVC - 11(上).DTO

    1.重要:javaScriptSerializer 无法识别被序列化的对象里各种属性是否存在  循环依赖 (System,Web.Script.Serialization.JavaScriptSeri ...

  4. Notice: Only variable references should be returned by reference(PHP版本兼容性问题)

    摘自:http://sushener.spaces.live.com/blog/cns!BB54050A5CFAFCDD!435.entry PHP5一个很让人恼火的一点就是BC(向后兼容)不是很理想 ...

  5. Pyqt Smtplib实现Qthread多线程发送邮件

    一. smtplib 的介绍 smtplib.SMTP([host[, port[, local_hostname[, timeout]]]])   SMTP类构造函数,表示与SMTP服务器之间的连接 ...

  6. 【PHP的异常处理【完整】】

    PHP的异常处理机制大多数和java的很相似,但是没有finally,而且还可以自定义顶级异常处理器:捕捉到异常信息后,会跳出try-catch块,如果catch中没有跳转的动作,则会继续执行下一条语 ...

  7. 【131031】struts 1 中 <html:form>

    <DIV>来看看 使用 ActionForm 这个主题,当时使用了一个静态表单网页:<BR>* form.htm<BR><BR><BR>&l ...

  8. SSH入门简单搭建例子

    因为公司涉及项目使用SSH,为了解SSH搭建方式和运作原理,就自己搭建了一个. 采用尽量以最少的JAR包,搭建一个简单的struts2+spring+hibernate环境,希望像我这样的入门者都能理 ...

  9. Oracle 监听器

    Oracle监听器listener是一个重要的数据库服务器组件,在整个Oracle体系结构中,扮演着重要的作用. 监听器Lisener功能 从当前的Oracle版本看,Listener主要负责下面的几 ...

  10. Shell拆分大文件

    需求:由于文件过大,不方便进行相关的操作,需要将其拆分成大小小于500000B,即488.28125k的文件.同时,为了保证文件的可读性,行内不可以分割,同时,由于内容是块状可读,按照日期进行分割的, ...