gRPC学习记录(五)--拦截器分析

gRPC学习记录(五)--拦截器分析

标签(空格分隔): javaWEB


对于此类调用拦截器是必不可少的,本篇就分析下拦截器的实现.(博主本来想分析源码的,但是水平不足,并发知识欠缺,看的不是很懂,哎,仍需努力),另外貌似不同版本有些差异,这里使用的是1.0.3版本.

1.一个拦截器的小例子

在分析之前先看一种设计.

有一个接口如下:

/**
 * 主调用接口
 */
public abstract class Client {
    public abstract void start(String say);
}
/**
 * 上述接口实现类
 */
public class ClientImp extends Client {
    @Override
    public void start(String say) {
        System.out.println(say);
    }
}

对此接口相关的转换器:

/**
 * 用于包装Client到另一个Client
 */
public abstract class ForwardingClient extends Client{
    //要包装的对象
    protected abstract Client delegate();

    @Override
    public void start(String say) {
        delegate().start(say);
    }
}
/**
 * 一个简单的包装实现类,必须要传入要包装的对象
 */
public class ForwardingClientImpl extends ForwardingClient{

    //被委托对象
    private final Client client;

    public ForwardingClientImpl(Client client) {
        this.client = client;
    }

    @Override
    protected Client delegate() {
        return client;
    }
}

然后在下列方法中调用:

public class InterceptTest {
    public static void main(String[] args) {
        Client client = new ClientImp();//主要想执行的方法
        //构造第一个拦截器
        Client intercept1 = new ForwardingClientImpl(client){
            @Override
            public void start(String say) {
                System.out.println("拦截器1");
                super.start(say);
            }
        };
        //构造第二个拦截器
        Client intercept2 = new ForwardingClientImpl(intercept1){
            @Override
            public void start(String say) {
                System.out.println("拦截器2");
                super.start(say);
            }
        };
        //执行主方法
        intercept2.start("这是要执行的方法");
    }
}

毫无疑问会输出

拦截器2
拦截器1
这是要执行的方法

分析一下针对Client接口,通过ForwardingClient可以实现自身的嵌套调用,从而达到了类似拦截器的效果.在gRPC中有很多类似的嵌套类,其本质和上面差不多,上面例子有助于对gRPC拦截器的掌握.

2.gRPC的ClientCall

该抽象类就是用来调用远程方法的,实现了发送消息和接收消息的功能,该接口由两个泛型ReqT和ReqT,分别对应着请求发送的信息,和请求收到的回复.
ClientCall抽象类主要有两个部分组成,一是public abstract static class Listener<T>用于监听服务端回复的消息,另一部分是针对客户端请求调用的一系列过程,如下代码流程所示:
该类中方法都是抽象方法,规定了整个调用顺序,如下:

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,然后实现委托嵌套调用,里面方法都如下代码所示:

@Override
  public void start(Listener<RespT> responseListener, Metadata headers) {
    delegate().start(responseListener, headers);
  }

  @Override
  public void request(int numMessages) {
    delegate().request(numMessages);
  }

那和之前的Demo一对比,拦截器怎么使用就变得很容易了.
创建一个客户端拦截器,其中为header添加了token参数.之所以要实现ClientInterceptor接口,因为Channel本身也是可以嵌套的类,所以创建ClientCall也是被一层一层的调用.

/**
 * 客户端拦截器
 * @author Niu Li
 * @date 2017/2/4
 */
//ClientInterceptor接口是针对ClientCall的创建进行拦截
public class ClientInterruptImpl implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
                                                               CallOptions callOptions, Channel next) {
        //创建client
        System.out.println("创建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,在此可以对header参数进行修改");
                Metadata.Key<String> token = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);
                headers.put(token,"123456");
                super.start(responseListener, headers);
            }
        };
    }
}

调用输出如下:

创建client1
拦截器1,在此可以对header参数进行修改

这是针对客户端调用前的拦截,对于客户端收到的回复拦截则通过ClientCall的静态内部类Listener来实现,该Listener也是可以嵌套的,其内有如下方法:

public void onHeaders(Metadata headers) {}
public void onMessage(T message) {}
public void onClose(Status status, Metadata trailers) {}
public void onReady() {}

对之前start方法改造下,让其判断返回的header中有没有传送过去的token,没有则该请求视为失败.

            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                System.out.println("拦截器1,在此可以对header参数进行修改");
                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("返回参数无token,关闭该链接");
                            super.onClose(Status.DATA_LOSS,headers);
                        }
                        super.onHeaders(headers);
                    }
                };
                super.start(forwardListener, headers);
            }

最后再Channel创建的时候使用intercept(new ClientInterruptImpl())加入拦截器这样就简单实现了客户端的拦截了.


3.gRPC的ServerCall

有一点要搞明白,ClientCall是针对客户端要调用的方法的,而ServerCall是针对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("执行server拦截器1,获取token");
        //获取客户端参数
        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,关闭此连接");
            call.close(Status.DATA_LOSS,headers);
        }
        //服务端写回参数
        ServerCall<ReqT, RespT> serverCall = new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
            @Override
            public void sendHeaders(Metadata headers) {
                System.out.println("执行server拦截器2,写入token");
                headers.put(token,tokenStr);
                super.sendHeaders(headers);
            }
        };
        return next.startCall(serverCall,headers);
    }
}

当服务端接收到请求的时候就会打印出来如下的日志.这样就实现了服务端接手前的拦截和写回时的拦截.

执行server拦截器1,获取token
收到的信息:world:0
执行server拦截器2,写入token

关于更多使用还在琢磨中,目前欠缺并发知识,所以下一步打算看看并发相关的资料.

附录:

相关代码: https://github.com/nl101531/JavaWEB

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,158评论 11 349
  • 又是一年清明节,对于在帝都打拼的大部分人来说,也许清明节只是多了一个小长假,可以出去游玩,与朋友聚会,或者在家休息...
    Michellesept阅读 554评论 1 4
  • 想了很久,如题。 在这里,我绝对不想说太多,我简单说一个小故事。 知了妈业余教小朋友画画,高大上一些就是“创意儿童...
    知了妈阅读 249评论 0 0