gRPC學(xué)習(xí)記錄(五)--攔截器分析
標(biāo)簽(空格分隔): javaWEB
對(duì)于此類調(diào)用攔截器是必不可少的,本篇就分析下攔截器的實(shí)現(xiàn).(博主本來想分析源碼的,但是水平不足,并發(fā)知識(shí)欠缺,看的不是很懂,哎,仍需努力),另外貌似不同版本有些差異,這里使用的是1.0.3版本.
1.一個(gè)攔截器的小例子
在分析之前先看一種設(shè)計(jì).
有一個(gè)接口如下:
/**
* 主調(diào)用接口
*/
public abstract class Client {
public abstract void start(String say);
}
/**
* 上述接口實(shí)現(xiàn)類
*/
public class ClientImp extends Client {
@Override
public void start(String say) {
System.out.println(say);
}
}
對(duì)此接口相關(guān)的轉(zhuǎn)換器:
/**
* 用于包裝Client到另一個(gè)Client
*/
public abstract class ForwardingClient extends Client{
//要包裝的對(duì)象
protected abstract Client delegate();
@Override
public void start(String say) {
delegate().start(say);
}
}
/**
* 一個(gè)簡(jiǎn)單的包裝實(shí)現(xiàn)類,必須要傳入要包裝的對(duì)象
*/
public class ForwardingClientImpl extends ForwardingClient{
//被委托對(duì)象
private final Client client;
public ForwardingClientImpl(Client client) {
this.client = client;
}
@Override
protected Client delegate() {
return client;
}
}
然后在下列方法中調(diào)用:
public class InterceptTest {
public static void main(String[] args) {
Client client = new ClientImp();//主要想執(zhí)行的方法
//構(gòu)造第一個(gè)攔截器
Client intercept1 = new ForwardingClientImpl(client){
@Override
public void start(String say) {
System.out.println("攔截器1");
super.start(say);
}
};
//構(gòu)造第二個(gè)攔截器
Client intercept2 = new ForwardingClientImpl(intercept1){
@Override
public void start(String say) {
System.out.println("攔截器2");
super.start(say);
}
};
//執(zhí)行主方法
intercept2.start("這是要執(zhí)行的方法");
}
}
毫無(wú)疑問會(huì)輸出
攔截器2
攔截器1
這是要執(zhí)行的方法
分析一下針對(duì)Client接口,通過ForwardingClient可以實(shí)現(xiàn)自身的嵌套調(diào)用,從而達(dá)到了類似攔截器的效果.在gRPC中有很多類似的嵌套類,其本質(zhì)和上面差不多,上面例子有助于對(duì)gRPC攔截器的掌握.
2.gRPC的ClientCall
該抽象類就是用來調(diào)用遠(yuǎn)程方法的,實(shí)現(xiàn)了發(fā)送消息和接收消息的功能,該接口由兩個(gè)泛型ReqT和ReqT,分別對(duì)應(yīng)著請(qǐng)求發(fā)送的信息,和請(qǐng)求收到的回復(fù).
ClientCall抽象類主要有兩個(gè)部分組成,一是public abstract static class Listener<T>
用于監(jiān)聽服務(wù)端回復(fù)的消息,另一部分是針對(duì)客戶端請(qǐng)求調(diào)用的一系列過程,如下代碼流程所示:
該類中方法都是抽象方法,規(guī)定了整個(gè)調(diào)用順序,如下:
call = channel.newCall(unaryMethod, callOptions);
call.start(listener, headers);
call.sendMessage(message);
call.halfClose();
call.request(1);
// wait for listener.onMessage()
在ClientCall的子類中有ForwardingClientCall<ReqT, RespT>
,該類的作用和之前的Demo一樣,用于包裝ClientCall,然后實(shí)現(xiàn)委托嵌套調(diào)用,里面方法都如下代碼所示:
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
delegate().start(responseListener, headers);
}
@Override
public void request(int numMessages) {
delegate().request(numMessages);
}
那和之前的Demo一對(duì)比,攔截器怎么使用就變得很容易了.
創(chuàng)建一個(gè)客戶端攔截器,其中為header添加了token參數(shù).之所以要實(shí)現(xiàn)ClientInterceptor
接口,因?yàn)镃hannel本身也是可以嵌套的類,所以創(chuàng)建ClientCall也是被一層一層的調(diào)用.
/**
* 客戶端攔截器
* @author Niu Li
* @date 2017/2/4
*/
//ClientInterceptor接口是針對(duì)ClientCall的創(chuàng)建進(jìn)行攔截
public class ClientInterruptImpl implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
//創(chuàng)建client
System.out.println("創(chuàng)建client1");
ClientCall<ReqT,RespT> clientCall = next.newCall(method,callOptions);
return new ForwardingClientCall<ReqT, RespT>() {
@Override
protected ClientCall<ReqT, RespT> delegate() {
return clientCall;
}
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
System.out.println("攔截器1,在此可以對(duì)header參數(shù)進(jìn)行修改");
Metadata.Key<String> token = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);
headers.put(token,"123456");
super.start(responseListener, headers);
}
};
}
}
調(diào)用輸出如下:
創(chuàng)建client1
攔截器1,在此可以對(duì)header參數(shù)進(jìn)行修改
這是針對(duì)客戶端調(diào)用前的攔截,對(duì)于客戶端收到的回復(fù)攔截則通過ClientCall的靜態(tài)內(nèi)部類Listener來實(shí)現(xiàn),該Listener也是可以嵌套的,其內(nèi)有如下方法:
public void onHeaders(Metadata headers) {}
public void onMessage(T message) {}
public void onClose(Status status, Metadata trailers) {}
public void onReady() {}
對(duì)之前start方法改造下,讓其判斷返回的header中有沒有傳送過去的token,沒有則該請(qǐng)求視為失敗.
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
System.out.println("攔截器1,在此可以對(duì)header參數(shù)進(jìn)行修改");
Metadata.Key<String> token = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);
headers.put(token,"123456");
Listener<RespT> forwardListener = new ForwardingClientCallListener.
SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onHeaders(Metadata headers) {
Metadata.Key<String> token = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);
if (!"123456".equals(headers.get(token))){
System.out.println("返回參數(shù)無(wú)token,關(guān)閉該鏈接");
super.onClose(Status.DATA_LOSS,headers);
}
super.onHeaders(headers);
}
};
super.start(forwardListener, headers);
}
最后再Channel創(chuàng)建的時(shí)候使用intercept(new ClientInterruptImpl())
加入攔截器這樣就簡(jiǎn)單實(shí)現(xiàn)了客戶端的攔截了.
3.gRPC的ServerCall
有一點(diǎn)要搞明白,ClientCall是針對(duì)客戶端要調(diào)用的方法的,而ServerCall是針對(duì)ClientCall的.看如下例子:
public class ServerInterruptImpl implements ServerInterceptor{
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
Metadata headers, ServerCallHandler<ReqT, RespT> next) {
System.out.println("執(zhí)行server攔截器1,獲取token");
//獲取客戶端參數(shù)
Metadata.Key<String> token = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);
String tokenStr = headers.get(token);
if (StringUtil.isNullOrEmpty(tokenStr)){
System.out.println("未收到客戶端token,關(guān)閉此連接");
call.close(Status.DATA_LOSS,headers);
}
//服務(wù)端寫回參數(shù)
ServerCall<ReqT, RespT> serverCall = new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
@Override
public void sendHeaders(Metadata headers) {
System.out.println("執(zhí)行server攔截器2,寫入token");
headers.put(token,tokenStr);
super.sendHeaders(headers);
}
};
return next.startCall(serverCall,headers);
}
}
當(dāng)服務(wù)端接收到請(qǐng)求的時(shí)候就會(huì)打印出來如下的日志.這樣就實(shí)現(xiàn)了服務(wù)端接手前的攔截和寫回時(shí)的攔截.
執(zhí)行server攔截器1,獲取token
收到的信息:world:0
執(zhí)行server攔截器2,寫入token
關(guān)于更多使用還在琢磨中,目前欠缺并發(fā)知識(shí),所以下一步打算看看并發(fā)相關(guān)的資料.
附錄:
相關(guān)代碼: https://github.com/nl101531/JavaWEB