文档章节

请求转发器实现原理

哈库纳
 哈库纳
发布于 2016/09/30 11:44
字数 1629
阅读 235
收藏 3
点赞 1
评论 0

    一个好的 Web 开发框架必然少不了接收 Action 请求这个重要的环节。我们也可以看到市面上不同的框架都有着自己特色的处理方式。当然 Hasor 在处理 Web 请求时也会有自己独特的处理方式。看过之后相信你一定会非常熟悉。

    说了这么多,让我们来看看如何使用 Hasor 来处理连入的请求吧。

    根据上面图可以看出,Hasor 在处理 Web 请求的时候采用了通用的处理方式。请求要首先进入口 Filter,然后 入口 Filter 负责调用 Dispatcher 把请求派发到对应的 Controller 上从而完成真个请求接入的工作。

一、入口

    那么 Dispatcher 是如何工作的呢?

    在 Hasor 的入口 RuntimeFilter 中可以看到除了做的最多的 init 初始化工作之外,最重要的就是下面这个方法,调用 Dispatcher 进行请求派发。

public class RuntimeFilter implements Filter {
    ...
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        ...
        this.processFilterPipeline(httpReq, httpRes, chain);
        ...
    }
    ...
    private void processFilterPipeline(final HttpServletRequest httpReq, final HttpServletResponse httpRes, final FilterChain chain) throws IOException, ServletException {
        this.filterPipeline.dispatch(httpReq, httpRes, chain);
    }

}

 

二、Dispatcher派发器

-请求拦截器

    FilterPipeline 派发器是一个接口,它的实现类是 ManagedFilterPipeline。派发器最重要的工作就是根据当前请求路径找到最适合的那个 Controller 然后把请求交给这个  Controller。

    ManagedFilterPipeline,有两个特别重要的过程一个是 initPipeline ,负责初始化整个基础数据。有了基础数据,接下来在匹配 Controller 时才有的选。另外一个重要的过程就是 dispatch 这个方法负责从众多 Controller 中找到需要的那一个然后执行调用。

    先说 init 过程,下面这个就是 init 过程的关键代码。其中我们可以看到 collectFilterDefinitions 方法负责把所有的 Controller 都收集到。然后执行对应的 init 方法。

public class ManagedFilterPipeline implements FilterPipeline {
    ...
    public synchronized void initPipeline(final WebAppContext appContext, final Map<String, String> filterConfig) throws ServletException {
        ...
        this.filterDefinitions = this.collectFilterDefinitions(appContext);
        for (FilterDefinition filterDefinition : this.filterDefinitions) {
            filterDefinition.init(appContext, filterConfig);
        }
        ...
    }
    ...
}

    dispatch,下面这个是主要逻辑。我们可以看到在初始化中得到的 filterDefinitions 并没有按照我们想想中的先筛选出一个子集合,然后在去执行调用。而是直接把请求送进了所有 Controller。其中 FilterChainInvocation 类的作用是充当过滤器链调度器。

public class ManagedFilterPipeline implements FilterPipeline {
    ...
    public void dispatch(HttpServletRequest request, HttpServletResponse response, FilterChain defaultFilterChain) {
        ...
        FilterChainInvocation invocation = new FilterChainInvocation(this.filterDefinitions, this.servletPipeline, defaultFilterChain);
        invocation.doFilter(dispatcherRequest, response);
        ...
    }
    ...
}

    dispatch 的入口方法中既然没有做筛选,那么一定是在 doFilter 中在执行时做了判断。这样的设计有一个很大的好处。就是节省资源开销。

    让我们想象一下,如果每个请求在进入 dispatch 的时都要对所有 Controller 遍历一遍求出最重需要的那一个。最后我们的到了想要的 Controller List 在循环执行一次调用。假设我们有 10 个 Controller 那么最多就要执行 20 次循环。这种方式实在是笨拙既浪费 CPU 不说还浪费内存,如果并发请求量很大的话。这种方式还会成为框架性能的瓶颈。

    因此 Hasor 在设计 dispatch 的时候充考虑到了这种情况,为了提升性能把遍历和判断改为一次循环一次判断。既省 CPU 还省内存,每次执行 dispatch 最多产生 10次循环。这样一来即环保又高效。

    下面代码中可以看到 matchesUri 方法的返回值直接影响到了 Controller 是否会被执行,如果当前的 Controller 没有命中就会通过 FilterChain 前往下一个。

class FilterDefinition extends AbstractServletModuleBinding {
    ...
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
        boolean serve = this.matchesUri(path);
        //
        Filter filter = this.getTarget();
        if (serve && filter != null) {
            filter.doFilter(request, response, chain);
        } else {
            chain.doFilter(httpRequest, response);
        }
    }
    ...
}

 

- 调用Controller

    前面我们一致都忽略了 Servlet 的处理,这里我们在讨论一下 Servlet。 Hasor 的 Servlet 处理都是封装到 ManagedServletPipeline 类里与 ManagedFilterPipeline 具有同等地位。

    不同的是 ManagedServletPipeline 接受的请求是在 ManagedFilterPipeline 执行过程的最后才会调用到它,如果在 Filter 执行过程中有任何一个环节没有继续执行 “chain.doFilter(httpRequest, httpResponse);” 那就表示后面不会执行到 ManagedServletPipeline。

    这一点也很好理解,毕竟 Filter 已经拦截了请求并做了相应的处理。后续可能就不需要在执行了。

 

三、为什么要用循环判断而不用Mapping做映射?

    这一节我们从一个小问题出发论一论正确性。

    这个问题应该是大家都会普遍问到的问题,首先从功能实现上来讲循环判断和 Mapping 映射都可以实现相同的功能。循环判断要比 Mapping 映射复杂一点,而且要稍微耗时一些。

    Mapping 通常是利用 HashMap 或者其它一些 Map 来实现,而这些 Map 都有一个共同的问题那就是 Hash碰撞。

    接下来我说一个实际的 Case。记得在开发 RSF 项目中,采用了 String -> Method  这种映射机制做缓存。省了每次都到 Class 中反射查询方法。原本上想增加运行效率,不料在单元测试中,发生了找不到方法的异常。几经排查,最后锁定了问题的元凶。就是那个 String -> Method 的 cache。

    在这个 case 中虽然没有产生什么后果,但是如果在线上环境上遇到恰好两遍方法入参、出参又一致。那么就会在你不知情的情况下,调用了一个错误的方法。而这个错误的方法却给出了你一个你认为正确的结果。

    进一步想象一下,如果是某个交易场景呢?

    可能有同学说了,概率太小了。不要大惊小怪。但是实际中我们不能说风险小就不去管它,一旦遇到风险怎么办呢?

    这就好比生一个健康的孩子,医院跟你说你的孩子 99.999% 是没有问题的。换句话说 10万个孩子里只会有一个有问题。 10万分之一的概率是多么小的概率。

    但是我要问的是你想做这个 10万分之一么?,又或者你想做 100万分之一那个人么?

    程序也是一个道理。风险在小只要它存在,就不能漠视。正确性高于一切!!!

© 著作权归作者所有

共有 人打赏支持
哈库纳

哈库纳

粉丝 953
博文 81
码字总数 149803
作品 4
杭州
后端工程师
Linux运维实战之DNS的高级配置(转发器、视图等)

上次博文我们具体配置了一台DNS服务器并实现了主辅之间的区域传送,本次博文我们来看看DNS的一些高级配置。 在进行DNS的高级配置之前,必须要理解DNS的原理(参见http://sweetpotato.blog.5...

土豆呼叫地瓜 ⋅ 2015/01/23 ⋅ 0

配置dns转发器的学习笔记

配置DNS转发器 原理: 配置需要: 一台dns服务器当转发器,ip地址为10.18.44.120 开始配置: 正常能够解析的dns服务器10.18.44.51 转发器10.18.44.120 测试转发器:10.18.44.171...

MoONq ⋅ 2017/12/27 ⋅ 0

SpringMVC架构原理分析

springmvc框架原理(掌握) 前端控制器、处理器映射器、处理器适配器、视图解析器 springmvc入门程序 目的:对前端控制器、处理器映射器、处理器适配器、视图解析器学习;非注解的处理器映射...

小小蒜头 ⋅ 2017/11/30 ⋅ 0

DNS安全和预防措施

DNS系统是所有互联网应用的基础,在网站运维中起着至关重要的作用,本文起一个抛砖引玉的作用,给大家探讨一下,域名解析的原理,域名解析系统常见的的攻击方法和预防措施。 在开始之前我先让...

wuhan ⋅ 2010/09/25 ⋅ 0

DNS安全和预防措施

DNS系统是所有互联网应用的基础,在网站运维中起着至关重要的作用,本文起一个抛砖引玉的作用,给大家探讨一下,域名解析的原理,域名解析系统常见的的攻击方法和预防措施。 在开始之前我先让...

wuhan ⋅ 2010/09/25 ⋅ 0

Bind的组成(续)

主配置文件;/etc/namd.conf include包含辅助配置文件; /etc/name.drfc1912.zons 主配置文件的格式; acl acl_name {}; Options {}; logging {}; zone view include file named-checkco......

杨铄 ⋅ 2017/11/21 ⋅ 0

Forward和Redirec两种转发的区别

一、概述 Forward和Redirect代表了两种请求转发方式:直接转发和间接转发。   直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资...

懂得-奉献 ⋅ 2016/10/21 ⋅ 0

Linux 系统通过 Squid 配置实现代理上网

Squid 介绍 普通代理:需要客户机在浏览器中指定代理服务器的地址、端口。 透明代理:适用于企业的网关主机(共享接入 Internet)中,客户机不需要指定 代理服务器地址、端口等信息,代理服务...

彭锐 ⋅ 05/31 ⋅ 0

SpringMvc 框架

简介 MVC 在B/S 系统下的应用 MVC 是一个设计模式 springMVC框架 组件: 前端控制器 DispatcherServlet 接收请求,响应结果。相当于转发器。 处理映射器 HandlerMapping 处理器适配器 Handle...

中柠檬 ⋅ 2016/11/18 ⋅ 0

从0开始写JavaWeb框架系列(6)从0开始写SamrtFrameWork:初始化框架

一、初始化思考 1.1、用过上面的过程和笔记我们创建了ClassHelper(程序启动加载所有基础目录下的Class对象)、BeanHelper(根据Class对象实例化Bean)、IocHelper(根据@Inject来控制反转,注入B...

AAASSSSddd ⋅ 2016/05/27 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

LVM

LVM: 硬盘划分分区成物理卷->物理卷组成卷组->卷组划分逻辑分区。 1.磁盘分区: fdisk /dev/sdb 划分几个主分区 输入t更改每个分区类型为8e(LVM) 使用partprobe生成分区的文件:如/dev/sd...

ZHENG-JY ⋅ 40分钟前 ⋅ 0

彻底删除Microsoft Office的方法

参照此链接彻底删除Office https://support.office.com/zh-cn/article/%e4%bb%8e-pc-%e5%8d%b8%e8%bd%bd-office-9dd49b83-264a-477a-8fcc-2fdf5dbf61d8?ui=zh-CN&rs=zh-CN&ad=CN......

Kampfer ⋅ 55分钟前 ⋅ 0

大盘与个股之间关系

大盘走多:积极出手 顺势加码 大盘走空: 少量出手 退场观望 大盘做头:逆势减码 少量操作 大盘做底 : 小量建仓 小量试单

guozenhua ⋅ 57分钟前 ⋅ 0

Day16 LVM(逻辑卷管理)与磁盘故障小案例

lvm详解 简述 LVM的产生是因为传统的分区一旦分区好后就无法在线扩充空间,也存在一些工具能实现在线扩充空间但是还是会面临数据损坏的风险;传统的分区当分区空间不足时,一般的解决办法是再...

杉下 ⋅ 今天 ⋅ 0

rsync实现多台linux服务器的文件同步

一、首先安装rsync,怎样安装都行,rpm,yum,还是你用源码安装都可以。因为我用的是阿里云的ESC,yum install rsync就ok了。 二、配置rsync服务 1.先建立个同步数据的帐号 123 groupadd r...

在下头真的很硬 ⋅ 今天 ⋅ 0

前端基础(三):函数

字数:1685 阅读时间:5分钟 函数定义 在最新的ES规范中,声明函数有4中方法: -函数声明 -函数表达式 -构造函数Function -生成器函数 1.函数声明 语法: function name([param[, param2 [....

老司机带你撸代码 ⋅ 今天 ⋅ 0

Java虚拟机的Heap监狱

在Java虚拟机中,我是一个位高权重的大管家,他们都很怕我,尤其是那些Java 对象,我把他们圈到一个叫做Heap的“监狱”里,严格管理,生杀大权尽在掌握。 中国人把Stack翻译成“栈”,把Hea...

java高级架构牛人 ⋅ 今天 ⋅ 0

Spring MVC基本概念

只写Controller

颖伙虫 ⋅ 今天 ⋅ 0

微软重金收购GitHub的背后逻辑原来是这样的

全球最大的开发者社区GitHub网站花落谁家的问题已经敲定,微软最终以75亿美元迎娶了这位在外界看来无比“神秘”的小家碧玉。尽管此事已过去一些时日,但整个开发者世界,包括全球各地的开源社...

linux-tao ⋅ 今天 ⋅ 0

磁盘管理—逻辑卷lvm

4.10-4.12 lvm 操作流程: 磁盘分区-->创建物理卷-->划分为卷组-->划分成逻辑卷-->格式化、挂载-->扩容。 磁盘分区 注: 创建分区时需要更改其文件类型为lvm(代码8e) 分区 3 已设置为 Linu...

弓正 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部