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是一个ScopedHandler,具有ScopedHandler相关的特性
ServletHandler又依赖FilterHolder、ServletHolder、ServletMapping、FilterMapping这四大组件(同时还会依赖一些公用组件,例如PathMappings、Trie树(Trie树在Jetty里面广泛应用,后面会集中分析))
Filter、Servlet会被包装称为FilterHolder、ServletHolder,每一个Servlet与路径的映射会被包装成ServletMapping,而Filter与路径或者Servlet名称的映射会被包装成FilterMapping
1 、Filter的创建
addFilter:
这个方法由外部生成好FilterHolder调用,通常是由ServletContextHanlder内部实现的Context类完成封装,将该FilterHolder传入,这里只是控制下并发,并去重,设置进_filters数组
setFilters会将当前ServletHandler容器中的所有FilterHolder设置上关联的ServletHandler,并将这些filters放到容器托管生命周期,同时更新_filters、_servlets数组的名称映射map,并清理链缓存,个人觉得这里比较鸡肋的地方是每次新加一个Filter都会导致这里重新刷一次命名map(命名map构建和filterChain缓存会在doStart、doScope分别讲解)。对于前期配置,容器并未启动时,我们需要注册大量的Filter,这时会频繁操作这个函数,有点多余
2 、Servlet的创建
和Filter创建几乎一致,只是换成了判断ServletHolder
逻辑和上面一致,唯一的差异就是换成了ServletHolder
3、FilterMapping的创建
FilterMapping的创建addFilterWithMapping有很多方法,重载的大部分方法会包装好FilterHolder传入核心方法public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet dispatches)
这里会首先获取到现在容器中的Filters,然后复制一份数组对象(浅拷贝),加锁去重判断是否存在,并设置这个FilterHolder到_filters数组,创建FilterMapping,传入必要的参数,调用addFilterMapping最核心方法
void addFilterMapping(FilterMapping mapping),该方法主要按照一个位置规则将FilterMapping插到合适的位置
拿到FilterMapping对应的FilterHolder的来源,主要是EMBEDDED、JAVAX_API,标识是通过编程方式层面添加还是非编程方式
如果当前容器中没有FilterMapping会将该FilterMapping放到第一个位置
如果已经存在FilterMapping,创建源是代码方式,会将该FilterMapping放到数组最后,否则如果是第一次添加,会直接放到_filters数组最后,不是第一次会放到当前_filters的游标之前。个人觉得Jetty这里的实现略显复杂,作为一个FilterMapping能按照某个顺序添加就行了,而Jetty搞了一个源的判断,并设置游标,— —|||,或许还需要再深入理解
另外还有一个void prependFilterMapping (FilterMapping mapping),这个方法将FilterMapping向前添加,逻辑和上面的向后添加很像
第一个也是放到数组第一个,不同的是在插入的位置为0,从数组头部开始,并从头部开始递增索引存放
我们看到添加完后都会调用setFilterMappings,逻辑几乎一致,不过由于FilterMapping不具有生命周期,因此updateBeans并不会有啥影响,如果启动会触发更新映射关系(这个后面会分析),清理filterChain缓存
4 、ServletMapping的创建
同样的addServletWithMapping逻辑和addFilterMapping几乎一致,差异就是操作的是ServletMapping,这里就不再讲了
5 、更新名称映射(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. 路径匹配规则:精确匹配、前缀匹配、后缀匹配
回顾完成后,接下来分析这里的逻辑就比较自然了
首先如果filterMappings为空,则filterPathMappings、filterNameMappings全部置空,很好理解
否则,遍历filterMappings处理。_filterPathMappings表示以路径映射的FilterMapping,_filterNameMappings表示以名称映射的FilterMapping;_filterPathMappings是一个数组,存放以路径映射的FilterMapping。_filterNameMappings是一个MultiMap,一个Filter名称映射多个FilterMapping(前面我们回顾的时候已经说到一个Filter可以映射多个FilterMapping)
当前FilterMapping如果是路径映射就加到_filterPathMappings,如果是名称映射就加到filterNameMappings
从上面看到定义了一个PathMappings,这是一个路径映射一个ServletHolder的类,servletPathMappings是一个路径映射ServletMapping的map
首先将servletMappings按照路径分组到sms里面
将分组后的servletMapping分别处理,同一路径下的最后一个ServletMapping或默认ServletMapping成为当前路径下的ServletMapping,同时会根据当前url的映射规则(精确匹配、前缀匹配、后缀匹配)放到PathMappings里面去
如果当前缓存不为空,会清掉
如果当前contextHandler已经启动了,会重新初始化一次Servlet和Filter
7 、ServletHandler启动
doStart:
获取当前线程对应的ServletContext,没有就创建一个,否则复用
获取当前Context对应的ServletContextHandler,默认情况并不存在
如果存在ContextHandler,就会从里面提取SecurityHandler,并把SecurityHandler的identityService保存下来(大家下来可以分析下为什么)
更新命名映射,更新mapping映射,上面已经讲过了,这里就不在赘述
获取以/开头的servletMapping,如果不存在并且要保证一个默认的Servlet处理器(默认是需要的),会将Default404Servlet加入到/路径上成为默认Servlet处理器,并更新映射
默认情况filterChain是需要缓存的,因此这里会重新初始化每个DispatcherType的缓存以及LRU列表,后续在doHandle里面详解
之后如果contextHandler还未完成初始化,则初始化
先启动并初始化filters,如果filter未创建,则自动根据heldClass创建,并创建filterConfig,执行filter.init初始化
启动并初始化servlets,调用initServlet初始化Servlet,确保Servlet实例存在,并创建ServletConfig,执行servlet.init
其他Holder的初始化,这里就不在讲解了
8 、ServletHandler.doScope
获取基础配置,如旧的servletPath、pathInfo等
接下来调用getMappedServlet获取该路径下的最佳匹配ServletHolder
获取到最佳匹配的ServletHolder后,会将servletPath、pathInfo这些信息提取出来,放到Request里面去,方便后续使用
最后还原就数据,这个和其他的Handler行为一致,这里就不在赘述
如果当前是一个路径过滤的请求,则从前面提到的PathMappings里面获取最佳匹配,PathMappings或根据所有的ServletMappings里面挨个匹配,根据mapping本身的分组(匹配规则),优先匹配精确匹配类型,其次是前缀匹配,最后是后缀匹配,每个匹配规则被分到不同的Trie树中,这样可以以最佳前缀匹配来定位到该路径的ServletHolder(后面我们会专门分析Trie树在Jetty中的应用)
如果当前不是一个以/开头的路径,那就考虑是从ServletNameMap里面获取(这里目前还没看到是什么场景)
9 、ServletHandler.doHandle
前面将最佳匹配ServletHolder放到了userIdentityScope,这里就直接取出来
对于以/开头的路径,如果有filterMappings映射关系,则需要获取filterChain,如果不存在匹配的filter链,则执行ServletHolder.handle,直接打到Servlet,否则执行FilterChain.doFilter
如果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,实现了FilterChain,doFilter里面会判断当前是否包含FilterHolder,如果有就会执行Filter.doFilter,会将下一个节点传入到里面,这样在业务层写的chain.doFilter,实际是执行了这个CachedChain的doFilter,如果没有Filter可以执行,那就直接执行ServletHolder的handle方法了
从上面可以看到是调用了ServletHolder里面包装的Servlet,执行Servlet.service方法,打到了业务层
ServletHandler最为Jetty中Handler体系里面最重要的处理器,它实现了基础的Servlet容器Filter、Servlet的处理能力,逻辑相对较复杂,需要结合Servlet规范来理解,细节还是非常多。本文并未完全将所有细节讲解,而是抽出了最核心的部分来阐述,有兴趣的读者可以下来自己深入理解。接下来的文章会带大家分析ServletContextHandler以及WebAppContext,从而让我们打造一个真正完整的Jetty Web服务~