Jetty9源码剖析 - Handler组件 - ServletHandler

转载自ph0ly:http://www.ph0ly.com

一、ServletHandler的概念

ServletHandler作为Servlet容器必不可少的重要成员之一,是整个Servlet容器实现的最复杂的一个处理器。它实现Servlet规范中Filter、Servlet的基本处理逻辑。从ServletHandler的官方注释看出,该Handler并未完全实现J2EE的特性,如果需要完整的J2EE特性,还得使用WebAppContext

二、应用场景

当一个应用需要处理Servlet及Filter的逻辑时,需要集成该Handler,对于Jetty容器来说,该Handler是必须使用的Handler,一般会利用WebAppContext处理器来集成,WebAppContext是一个ServletContextHandler,而ServletContextHandler集成了3大组件:ContextHandler、ServletHandler、SessionHandler(SecurityHandler暂时忽略)。对于Spring Boot框架会自动使用WebAppContext来帮助我们创建Jetty容器Handler

三、继承体系

ServletHandler组件继承体系

可以看到ServletHandler是一个ScopedHandler,具有ScopedHandler相关的特性

ServletHandler及周边组件关系

ServletHandler又依赖FilterHolder、ServletHolder、ServletMapping、FilterMapping这四大组件(同时还会依赖一些公用组件,例如PathMappings、Trie树(Trie树在Jetty里面广泛应用,后面会集中分析))

Filter、Servlet会被包装称为FilterHolder、ServletHolder,每一个Servlet与路径的映射会被包装成ServletMapping,而Filter与路径或者Servlet名称的映射会被包装成FilterMapping

四、源码剖析

1 、Filter的创建

ServletHandler.addFilter

addFilter:

这个方法由外部生成好FilterHolder调用,通常是由ServletContextHanlder内部实现的Context类完成封装,将该FilterHolder传入,这里只是控制下并发,并去重,设置进_filters数组

ServletHandler.setFilters

setFilters会将当前ServletHandler容器中的所有FilterHolder设置上关联的ServletHandler,并将这些filters放到容器托管生命周期,同时更新_filters、_servlets数组的名称映射map,并清理链缓存,个人觉得这里比较鸡肋的地方是每次新加一个Filter都会导致这里重新刷一次命名map(命名map构建和filterChain缓存会在doStart、doScope分别讲解)。对于前期配置,容器并未启动时,我们需要注册大量的Filter,这时会频繁操作这个函数,有点多余

2 、Servlet的创建

ServletHandler.addServlet

和Filter创建几乎一致,只是换成了判断ServletHolder

ServletHandler.setServlets

逻辑和上面一致,唯一的差异就是换成了ServletHolder

3、FilterMapping的创建

ServletHandler.addFilterWithMapping

FilterMapping的创建addFilterWithMapping有很多方法,重载的大部分方法会包装好FilterHolder传入核心方法public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet dispatches)

这里会首先获取到现在容器中的Filters,然后复制一份数组对象(浅拷贝),加锁去重判断是否存在,并设置这个FilterHolder到_filters数组,创建FilterMapping,传入必要的参数,调用addFilterMapping最核心方法

ServletHandler.addFilterWithMapping

void addFilterMapping(FilterMapping mapping),该方法主要按照一个位置规则将FilterMapping插到合适的位置

拿到FilterMapping对应的FilterHolder的来源,主要是EMBEDDED、JAVAX_API,标识是通过编程方式层面添加还是非编程方式

如果当前容器中没有FilterMapping会将该FilterMapping放到第一个位置

如果已经存在FilterMapping,创建源是代码方式,会将该FilterMapping放到数组最后,否则如果是第一次添加,会直接放到_filters数组最后,不是第一次会放到当前_filters的游标之前。个人觉得Jetty这里的实现略显复杂,作为一个FilterMapping能按照某个顺序添加就行了,而Jetty搞了一个源的判断,并设置游标,— —|||,或许还需要再深入理解

ServletHandler.prependFilterMapping

另外还有一个void prependFilterMapping (FilterMapping mapping),这个方法将FilterMapping向前添加,逻辑和上面的向后添加很像

第一个也是放到数组第一个,不同的是在插入的位置为0,从数组头部开始,并从头部开始递增索引存放

ServletHandler.setFilterMappings

我们看到添加完后都会调用setFilterMappings,逻辑几乎一致,不过由于FilterMapping不具有生命周期,因此updateBeans并不会有啥影响,如果启动会触发更新映射关系(这个后面会分析),清理filterChain缓存

4 、ServletMapping的创建

ServletHandler.addServletWithMapping

同样的addServletWithMapping逻辑和addFilterMapping几乎一致,差异就是操作的是ServletMapping,这里就不再讲了

5 、更新名称映射(updateNameMappings)

ServletHandler.updateNameMappings

前面提到了在添加filters时会调用更新名称映射,主要分为两个部分。第一个是Filter名称与FilterHolder的映射,第二个是Servlet名称与ServletHolder的映射,比较简单,就不细讲了

6 、更新mapping映射(updateMappings)

最复杂的是updateMappings,它主要完成Filter的名称和路径映射,Servlet的路径映射

首先我们回顾下Filter、Servlet的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<filter>

    <filter-name>Filter1</filter-name>

    <filter-class>com.flycat.Filter1</filter-class>

    <init-param>

        <param-name>excludes</param-name>

        <param-value>test</param-value>

    </init-param>

</filter>

<filter-mapping>

    <filter-name>Filter1</filter-name>

    <servlet-name>HelloServlet</servlet-name>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

<filter-mapping>

    <filter-name>Filter1</filter-name>

    <url-pattern>/*</url-pattern>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

<servlet>

    <servlet-name>HelloServlet</servlet-name>

    <servlet-class>com.flycat.HelloServlet</servlet-class>

</servlet>

<servlet-mapping>

    <servlet-name>HelloServlet</servlet-name>

    <url-pattern>*.do</url-pattern>

</servlet-mapping>

<servlet-mapping>

    <servlet-name>HelloServlet</servlet-name>

    <url-pattern>/action/*</url-pattern>

</servlet-mapping>

从上面的配置中我们可以看到

1. 一个Filter可以拦截多个Url路径或者拦截多个Servlet(按照名称),一个过滤路径(url-pattern)可能映射多个Filter

2. 一个Servlet可以处理多个url-pattern,即一个过滤路径(url-pattern)可以映射多个Servlet

3. 路径匹配规则:精确匹配、前缀匹配、后缀匹配

回顾完成后,接下来分析这里的逻辑就比较自然了

ServletHandler.updateMappings

首先如果filterMappings为空,则filterPathMappings、filterNameMappings全部置空,很好理解

否则,遍历filterMappings处理。_filterPathMappings表示以路径映射的FilterMapping,_filterNameMappings表示以名称映射的FilterMapping;_filterPathMappings是一个数组,存放以路径映射的FilterMapping。_filterNameMappings是一个MultiMap,一个Filter名称映射多个FilterMapping(前面我们回顾的时候已经说到一个Filter可以映射多个FilterMapping)

 

当前FilterMapping如果是路径映射就加到_filterPathMappings,如果是名称映射就加到filterNameMappings

ServletHandler.updateMappings

从上面看到定义了一个PathMappings,这是一个路径映射一个ServletHolder的类,servletPathMappings是一个路径映射ServletMapping的map

首先将servletMappings按照路径分组到sms里面

将分组后的servletMapping分别处理,同一路径下的最后一个ServletMapping或默认ServletMapping成为当前路径下的ServletMapping,同时会根据当前url的映射规则(精确匹配、前缀匹配、后缀匹配)放到PathMappings里面去

ServletHandler.updateMappings

如果当前缓存不为空,会清掉

如果当前contextHandler已经启动了,会重新初始化一次Servlet和Filter

7 、ServletHandler启动

ServletHandler.doStart

doStart:

获取当前线程对应的ServletContext,没有就创建一个,否则复用

获取当前Context对应的ServletContextHandler,默认情况并不存在

如果存在ContextHandler,就会从里面提取SecurityHandler,并把SecurityHandler的identityService保存下来(大家下来可以分析下为什么)

更新命名映射,更新mapping映射,上面已经讲过了,这里就不在赘述

获取以/开头的servletMapping,如果不存在并且要保证一个默认的Servlet处理器(默认是需要的),会将Default404Servlet加入到/路径上成为默认Servlet处理器,并更新映射

默认情况filterChain是需要缓存的,因此这里会重新初始化每个DispatcherType的缓存以及LRU列表,后续在doHandle里面详解

之后如果contextHandler还未完成初始化,则初始化

ServletHandler.initialize

先启动并初始化filters,如果filter未创建,则自动根据heldClass创建,并创建filterConfig,执行filter.init初始化

启动并初始化servlets,调用initServlet初始化Servlet,确保Servlet实例存在,并创建ServletConfig,执行servlet.init

其他Holder的初始化,这里就不在讲解了

8 、ServletHandler.doScope

ServletHandler.doScope

获取基础配置,如旧的servletPath、pathInfo等

接下来调用getMappedServlet获取该路径下的最佳匹配ServletHolder

获取到最佳匹配的ServletHolder后,会将servletPath、pathInfo这些信息提取出来,放到Request里面去,方便后续使用

最后还原就数据,这个和其他的Handler行为一致,这里就不在赘述

ServletHandler.getMappedServlet

如果当前是一个路径过滤的请求,则从前面提到的PathMappings里面获取最佳匹配,PathMappings或根据所有的ServletMappings里面挨个匹配,根据mapping本身的分组(匹配规则),优先匹配精确匹配类型,其次是前缀匹配,最后是后缀匹配,每个匹配规则被分到不同的Trie树中,这样可以以最佳前缀匹配来定位到该路径的ServletHolder(后面我们会专门分析Trie树在Jetty中的应用)

如果当前不是一个以/开头的路径,那就考虑是从ServletNameMap里面获取(这里目前还没看到是什么场景)

9 、ServletHandler.doHandle

ServletHandler.doHandle

前面将最佳匹配ServletHolder放到了userIdentityScope,这里就直接取出来

对于以/开头的路径,如果有filterMappings映射关系,则需要获取filterChain,如果不存在匹配的filter链,则执行ServletHolder.handle,直接打到Servlet,否则执行FilterChain.doFilter

ServletHandler.getFilterChain

如果cache不为空,则直接从缓存获取

根据filter路径看是否能匹配上当前的路径及DispatcherType,将匹配上的filter存起来

然后查找filter名称映射里面是否能匹配上当前这个ServletHolder的,将匹配上的放到上面那个集合

如果没有找到任何的匹配的filter,就返回null

否则,构建执行链

newCachedChain,会创建CachedChain,实现了FilterChain,它会将这些filter列表中的FilterHolder构建一条链,在链的末端是当前这个ServletHolder,即:Filter1 -> Filter2 -> Filter3 -> Servlet

然后会处理缓存的大小,如果超过最大大小(默认512),会执行LRU策略,将超过大小的全部FilterChain从LRU中移除,并从_chainCache中删掉。清理完成后将新的加到这里面去

 

如果设置了当前容器不走FilterChain缓存,就会创建Chain这种非缓存类型的执行链(默认是缓存的)

CachedChain

再回头看下CachedChain,实现了FilterChain,doFilter里面会判断当前是否包含FilterHolder,如果有就会执行Filter.doFilter,会将下一个节点传入到里面,这样在业务层写的chain.doFilter,实际是执行了这个CachedChain的doFilter,如果没有Filter可以执行,那就直接执行ServletHolder的handle方法了

ServletHolder.handle

从上面可以看到是调用了ServletHolder里面包装的Servlet,执行Servlet.service方法,打到了业务层

五、总结

ServletHandler最为Jetty中Handler体系里面最重要的处理器,它实现了基础的Servlet容器Filter、Servlet的处理能力,逻辑相对较复杂,需要结合Servlet规范来理解,细节还是非常多。本文并未完全将所有细节讲解,而是抽出了最核心的部分来阐述,有兴趣的读者可以下来自己深入理解。接下来的文章会带大家分析ServletContextHandler以及WebAppContext,从而让我们打造一个真正完整的Jetty Web服务~