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. 关于快捷键 Ctrl+Alt+[方向键] 的知识

    在用PS作图时使用 Ctrl+Alt+[方向键]  组合建时屏幕莫名翻转, 平时电脑懒得维护所以略卡,我不会说一般早上起床摁了开机去上完厕所回来还--咳咳 刚按下时瞬间一黑,再黑,,继续黑--真是大吃 ...

  2. iOS-OC-基本控件之UITextField

    UITextField IOS开发中必不可少的基本控件,本文主要是列出常用的属性及方法(注XCode版本为7.2) 文本框,可以理解为输入框或者显示框,即用户可以往里面输入文字或图片,可以输入当然也可 ...

  3. MVC - 17.OA项目

          1.分层   2.项目依赖关系 MODEL IDAL -> MODEL DAL -> IDAL,MODEL,EntityFramewrok(注意和MODEL里的版本要一致),S ...

  4. probe函数何时调用的

    转自:http://blog.csdn.net/xiafeng1113/article/details/8030248 Linux中 probe函数何时调用的 所以的驱动教程上都说:只有设备和驱动的名 ...

  5. 使用Mybatis-Generator自动生成Dao、Model、Mapping相关文件(转)

    Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,由于手动书写很容易出错,我们可以利用Mybatis-Generator来帮我们自动生成文件. 1.相关文 ...

  6. java中常用的工具类(二)

    下面继续分享java中常用的一些工具类,希望给大家带来帮助! 1.FtpUtil           Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...

  7. Linux發送郵件

    1.直接使用shell當編輯器 [root@phburdb1 mail]# mail -s "Hello World" juncai.chen@innolux.comHello j ...

  8. PHPCMS 多站点管理切换问题

    打开系统函数库global.func.php 可以看到获取站点ID的函数如下 /** * 获取当前的站点ID */ function get_siteid() { static $siteid; if ...

  9. java 存储对象

    一.存储区域: 1)寄存器.这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器根据需求进行分配.你不能直接控制,也不能在程序中感觉到寄存器存在的任何 ...

  10. 10 个学习iOS开发的最佳网站(转)

    10 个学习iOS开发的最佳网站 作者 jopen 2012-09-26 08:59:56 1) Apple Learning Objective C Objective-C,通常写作ObjC和较少用 ...