1、过滤器使用场景

  做业务的时候我们经常要使用过滤器或者拦截器(听这口音就是从Java过来的)。常见的场景如一个HTTP请求,需要经过鉴权过滤器、白名单校验过滤、参数验证过滤器等重重关卡最终拿到数据。

  Java使用过滤器很简单。XML时代,只要添加一个过滤器配置再新建一个实现了Filter接口的xxxFilter实现类;Java Configuration时代,只要在xxxConfiguration配置类中声明一个Filter注解,如果想设置Filter的执行顺序,加上Order注解就行了。

  Java的过滤器实在太方便也太好用了。

  以至于在Java有关过滤器的面试题中,只有类似于“过滤器的使用场景有哪些?”,“过滤器和拦截器有什么区别?“,几乎很少听到”你知道过滤器是怎么实现的吗?“,”如果让你实现一个过滤器,你会怎么做?“这样的题目。

2、使用过滤器的场景特征

如同上面过滤器的例子,我们发现过滤器有一些特征:

  1、入参一样,比如HTTP请求的过滤器的入参就是ServletRequest对象

  2、返回值类型相同,比如都是true或者false,或者是链接到下一个过滤器或者return。

如下是Java实现的CORS过滤器

import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; public class CORSFilter implements Filter { @Override
public void doFilter(ServletRequest reserRealmq, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) reserRealmq;
HttpServletResponse response = (HttpServletResponse) res; String currentOrigin= request.getHeader("Origin");
if (!StringUtils.isEmpty(currentOrigin)) {
response.setHeader("Access-Control-Allow-Origin", currentOrigin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Cache-Control, Expires, Content-Type, X-E4M-With, Index-Url");
} // return http status 204 if OPTIONS requst
if ("OPTIONS".equals(request.getMethod())){
response.setStatus(HttpStatus.NO_CONTENT.value());
}else {
chain.doFilter(reserRealmq, res);
}
} @Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void destroy() { }
}

  

凡是具有这种特征的需求,我们都可以抽象为过滤器进行实现(Java里面称为责任链模式)。

下面就来说说,基于Go语言如何实现一个过滤器。

3、简单实现

  过滤器本质就是一堆条件判定,最直观的过滤方案就是创建几个方法,针对每个方法的返回结果判定,如果返回为false则终止请求,如果为true则继续执行下一个过滤器。

package main

import (
"context"
) func main() {
ctx := context.TODO() if continued := F1(ctx); !continued {
...
return
} if continued := F2(ctx); !continued {
...
return
} if continued := F3(ctx); !continued {
...
return
}
} func F1(ctx context.Context) bool {
...
return true
} func F2(ctx context.Context) bool {
...
return true
} func F3(ctx context.Context) bool {
...
return false
}

  

该版本从功能上说,完全符合过滤器的要求。

但是从代码层面来说,有几个问题:

  1、复用性较差。main函数中对于各个过滤器的判定,除了函数名不一样,其他逻辑都一样,可以考虑抽象重用。

  2、可扩展性较差。因为有些代码复用性差,导致代码不好扩展,如果这时候添加、删除过滤器或者调整过滤器执行顺序,代码都需要较大改动才能实现。

  3、难以维护。不用多说。

4、重构实现

package main

import (
"context"
"fmt"
) type MyContext struct {
context.Context
KeyValue map[string]bool
} type FilterFunc func(*MyContext) bool type FilterFuncChain []FilterFunc type CombinedFunc struct {
CF FilterFuncChain
MyCtx *MyContext
} func main() {
myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}} cf := CombinedFilter(&myContext, F1, F2, F3);
DoFilter(cf)
} func DoFilter(cf *CombinedFunc) {
for _, f := range cf.CF {
res := f(cf.MyCtx)
fmt.Println("result:", res)
if res == false {
fmt.Println("stopped")
return
}
}
} func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
return &CombinedFunc{
CF: ff,
MyCtx: ctx,
}
} func F1(ctx *MyContext) bool {
ctx.KeyValue["key"] = true
fmt.Println(ctx.KeyValue["key"]) return ctx.KeyValue["key"]
} func F2(ctx *MyContext) bool {
ctx.KeyValue["key"] = false
fmt.Println(ctx.KeyValue["key"]) return ctx.KeyValue["key"]
} func F3(ctx *MyContext) bool {
ctx.KeyValue["key"] = false
fmt.Println(ctx.KeyValue["key"]) return ctx.KeyValue["key"]
}

代码不长,我们一块块分析。

4.1 自定义的Context

  这里我使用了自定义的Context,重新定义一个MyContext的结构体,其中组合了标准库中的Context,即具备标准库Context的能力。

  这里MyContext是作为数据载体在各个过滤器之间传递。没有用标准库的Context,采用自定义的Context主要是为了说明我们可以根据需要扩展MyContext,通过扩展MyContext添加任何我们需要的参数。这里添加的是一个map键值对。我们可以将每个过滤器处理的结果存入这个map中,再传递到下一个过滤器。

myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}}

上面的等价写法还可以是

ctx := context.TODO()
myContext := context.WithValue(ctx, "key", "value")

这里充分利用了Context的WithValue的用法,有兴趣可以去看下,这是Context创建map键值对的方式。

4.2 充分利用Go的type的特性

type FilterFunc func(*MyContext) bool

  前面在使用过滤的场景特种中提到,过滤器的入参和返回值都是一样的。所以这里我们利用Go的type特性,将这种过滤器函数定义为一个变量FilterFunc

  这一特性对于精简代码起到了关键性的作用。且看

cf := CombinedFilter(&myContext, F1, F2, F3);

func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
return &CombinedFunc{
CF: ff,
MyCtx: ctx,
}
}

因为这里的F1、F2和F3都有相同入参和返回值,所以抽象为FilterFunc,并使用变长参数的FilterFunc统一接收。

CombinedFilter不仅可以加F1、F2和F3,后面还可以有F4、F5...

type FilterFuncChain []FilterFunc

  这里的抽象也是同样的道理。

  如果之前写过Java,这里是不是已经看到了Filter接口的影子。其实这里的FilterFunc可以等价于Java里面的Filter接口,接口是一种约束一种契约,Filter定义了如果要实现该接口必须要实现接口定义的方法。

package javax.servlet;

import java.io.IOException;

/**
* A FilterChain is an object provided by the servlet container to the developer
* giving a view into the invocation chain of a filtered request for a resource.
* Filters use the FilterChain to invoke the next filter in the chain, or if the
* calling filter is the last filter in the chain, to invoke the resource at the
* end of the chain.
*
* @see Filter
* @since Servlet 2.3
**/ public interface FilterChain { /**
* Causes the next filter in the chain to be invoked, or if the calling
* filter is the last filter in the chain, causes the resource at the end of
* the chain to be invoked.
*
* @param request
* the request to pass along the chain.
* @param response
* the response to pass along the chain.
*
* @throws IOException if an I/O error occurs during the processing of the
* request
* @throws ServletException if the processing fails for any other reason * @since 2.3
*/
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException; }

  

4.3 遍历执行过滤器

  因为有了上面的特性,我们才能将这些过滤器存入切片然后依次执行,如下

func DoFilter(cf *CombinedFunc) {
for _, f := range cf.CF {
res := f(cf.MyCtx)
fmt.Println("result:", res)
if res == false {
fmt.Println("stopped")
return
}
}
}

在执行的过程中,如果我们发现如果返回值为false,则表示没有通过某个过滤器校验,则退出也不会继续执行后面的过滤器。

5、继续改进

既然MyContext中的map集合可以存储各个Filter的执行情况,而且可以在各个过滤器之间传递,我们甚至可以省略FilterFunc函数的返回值,改进后如下

package main

import (
"context"
"fmt"
) type MyContext struct {
context.Context
KeyValue map[string]bool
} type FilterFunc func(*MyContext) type FilterFuncChain []FilterFunc type CombinedFunc struct {
CF FilterFuncChain
MyCtx *MyContext
} func main() {
myContext := MyContext{Context: context.TODO(), KeyValue: map[string]bool{"key": false}} cf := CombinedFilter(&myContext, F1, F2, F3);
DoFilter(cf)
} func DoFilter(cf *CombinedFunc) {
for _, f := range cf.CF {
f(cf.MyCtx)
continued := cf.MyCtx.KeyValue["key"]
fmt.Println("result:", continued)
if !continued {
fmt.Println("stopped")
return
}
}
} func CombinedFilter(ctx *MyContext, ff ...FilterFunc) *CombinedFunc {
return &CombinedFunc{
CF: ff,
MyCtx: ctx,
}
} func F1(ctx *MyContext) {
ctx.KeyValue["key"] = true
fmt.Println(ctx.KeyValue["key"])
//return ctx.KeyValue["key"]
} func F2(ctx *MyContext) {
ctx.KeyValue["key"] = false
fmt.Println(ctx.KeyValue["key"])
//return ctx.KeyValue["key"]
} func F3(ctx *MyContext) {
ctx.KeyValue["key"] = false
fmt.Println(ctx.KeyValue["key"])
//return ctx.KeyValue["key"]
}

  

6、总结

基于Go语言造轮子实现一个过滤器的雏形,通过实现一个相对优雅可扩展的过滤器熟悉了type的用法,Context.WithValue的作用。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Go语言学习——如何实现一个过滤器的更多相关文章

  1. C语言学习-01第一个C语言程序

    一 C语言的历史 C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. 尽管C语言提供 ...

  2. python语言学习3 ——第一个python程序

    输入exit即退出,这样写的缺点是没有保存已经写的代码,下次需要重新写

  3. 《C语言入门1.2.3—一个老鸟的C语言学习心得》—清华大学出版社炮制的又一本劣书及伪书

    <C语言入门1.2.3—一个老鸟的C语言学习心得>—清华大学出版社炮制的又一本劣书及伪书 [薛非评] 区区15页,有80多个错误. 最严重的有: 通篇完全是C++代码,根本不是C语言代码. ...

  4. C语言学习一个月后感想

    C语言学习一个月后感想 感谢李晓东老板及计算机工程师联盟的学长学姐和某神秘同级同学的辛勤指导,感谢宋雨田的督促和陪伴. 初识C的1..体会 我本以为凭借瓜皮思维和花里胡哨操作可以让我熟练地学习语言,现 ...

  5. C学习笔记-第一个C语言程序

    第一个C语言程序 #include<stdio.h> //1 int main() //2 { printf("This is a C language"); //3 ...

  6. angularjs学习第二天笔记---过滤器

    您好,我是一名后端开发工程师,由于工作需要,现在系统的从0开始学习前端js框架之angular,每天把学习的一些心得分享出来,如果有什么说的不对的地方,请多多指正,多多包涵我这个前端菜鸟,欢迎大家的点 ...

  7. C语言学习 第八次作业总结

    本次作业其实没有新的内容,主要就是复习上一次的一维数组的相关内容.冯老师布置了5道题目,其中涉及到一些比较简单的排序或者是查找的方法.因为数据很少,所以直接使用for循环遍历就可以了. 关于本次作业, ...

  8. C语言学习 第七次作业总结

    C语言学习 第七次作业总结 数组可以分为数组和多下标数组(在传统的国内C语言书本中,将其称为二/多维数组). 数组名称 在之前的课程中,大家应该都有印象,对于int a这样的定义,会为变量 a 声明一 ...

  9. 技能收获与C语言学习

    你有什么技能比大多人(超过90%以上)更好? 我会的东西很多,喜欢的东西太多,但是很遗憾广而不专,会而不精.学了很多东西我都是为了娱乐,因为以前我们那里过于强调学习,很多爱好也都被扼杀在摇篮里.我觉得 ...

随机推荐

  1. java基础知识必备(一)

    一,开发前奏        a.Java语言的发展历史.       Java语言是美国Sun公司(Stanford University Network/斯坦福大学网络公司),       在199 ...

  2. C++ 过滤出字符串的中文(GBK,UTF-8)

    最近在处理游戏敏感词之类的东西,为了加强屏蔽处理,所以需要过滤掉字符串中的除汉字之外的是其他东西如数字,符号,英文字母等. 首先我查阅资料并写了个函数: 示例:返回输入字符串中汉字的个数: std:: ...

  3. 浅谈JMM

    概述 JMM的全称是Java Memory Model(Java内存模型) JMM的关键技术点都是围绕着多线程的原子性.可见性和有序性来建立的,这也是Java解决多线程并行机制的环境下,定义出的一种规 ...

  4. 使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理

    现在公司测试中有一个痛点是每次测试人员提完bug后,需要定期去提醒开发人员查看禅道的bug记录及修复bug. 导致测试人员在项目测试中不仅要测试整个软件,还要负起实时监督提醒功能的“保姆角色”,身心疲 ...

  5. Scrapy框架安装失败解决办法

    安装报错信息 正常安装: pip3 install scrapy 出现报错信息如下: 两种解决办法 第一种方法 最根本得解决办法 需要我们安装  Microsoft Visual C++ 14.0  ...

  6. 抓取崩溃的log日志

    1.下载adb工具包 也就是解锁软件,如果要解锁的话,需确认有fastboot 安装jdk.sdk 2.注意事项 请确保电脑上只连接了一台手机设备(最好只连接一条USB线),同时确保手机已开启USB调 ...

  7. Docker入门学习笔记

    Docker 什么是Docker 虚拟化技术 在计算机中,虚拟化是一种资源管理技术,将计算机中的各种实体资源如:CPU.硬盘.内存等予以抽象.转换后呈现出来打破实体结构间的不可切割的障碍,使用户可以比 ...

  8. 一份新的lilypond谱子,能设置页边距和设置换页符了

    给学生做的一份乐谱,这回能设置页边距了,以及设置换页符了. 顺带能设置一些代码片段(snippet),可以用热键代替使用 设置页边距的snippet: \paper { %双引号里面填页面大小 #(s ...

  9. C程序设计(第四版)课后习题完整版 谭浩强编著

    //复习过程中,纯手打,持续更新,觉得好就点个赞吧. 第一章:程序设计和C语言 习题 1.什么是程序?什么是程序设计? 答:程序就是一组计算机能识别和执行的指令.程序设计是指从确定任务到得到结果,写出 ...

  10. 【原创】TextCNN原理详解(一)

    ​ 最近一直在研究textCNN算法,准备写一个系列,每周更新一篇,大致包括以下内容: TextCNN基本原理和优劣势 TextCNN代码详解(附Github链接) TextCNN模型实践迭代经验总结 ...