博客已迁移至 http://www.ph0ly.com 欢迎大家访问新的网站,文章更加齐全~
一、ScopedHandler的概念
在Jetty生态中,特别是Handler组件里面,不得不提ScopedHandler,Handler体系里面很大部分核心组件都继承了ScopedHandler,例如ContextHandler、ServletHandler、SessionHandler等。
ScopedHandler其实就是责任链的变种,常规的责任链声明一个共享方法,自己完成后调用下一个链。ScopedHandler也不例外,不过它是一个定制化的责任链处理器,他会先从链顶端节点开始执行每个节点的前置准备工作(doScope),之后再回到链顶端,开始执行逐个节点真正的处理方法(doHandle)
二、应用场景
假设有ServletContextHandler、SessionHandler、ServletHandler 3个类型都是ScopedHandler,在启动的时候我们可以将ServletContextHandler作为第一个执行的节点,SessionHandler作为第二个执行的节点,ServletHandler作为第三个执行节点,它们分别会执行各自的doScope准备数据,之后挨个执行doHandle完成执行的处理,这样就能完成一个链式处理。特别要注意的是doHandle的触发通常是doScope里面,而不是直接就被调用。个人觉得Jetty这个链还是比较晦涩,特别是用HandlerWrapper的特性绑定一个handler到内部,然后用容器托管,完成链的构建(参考ServletContextHandler的doStart方法),有点重
三、继承体系
我们可以从继承体系看到ScopedHandler实际就是一个HandlerWrapper,子类有SessionHandler、ContextHandler、ServletHandler,同时它的类标记为abstract,所以可以看出ScopeHandler是一个半成品
四、源码剖析
ScopedHandler用一个静态变量__outScope,类型为ThreadLocal,保存每个线程对应的顶端链节点。_outScope表示当前链的顶端链节点,_nextScope表示当前节点的下一个节点
doStart:设置当前节点的顶级节点_outScope,然后调用父类的doStart,完成容器中孩子的启动,之后从当前handler的孩子里面查找ScopedHandler,将第一个孩子(注意getChildHandlerByClass其实是拿当前容器里面所有ScopedHandler类型的对象,通常第一个孩子肯定是我们前面设置的handler)设置为_nextScope。finally里面判断如果是顶端节点,则将该线程绑定的顶端节点设置回null,防止后续线程复用的情况出现错误的顶端链
handle:处理方法,如果当前节点没有外层节点,则执行doScope,如果有则执行doHandle,该方法会触发链顶端节点的的doScope,然后挨个doScope完成后,在该方法里面回到顶端节点,执行其doHandle方法,再接着挨个节点doHandle直至最终完成调用
doScope:实现一个模板方法,调用nextScope完成下一节点处理路由
doHandle:抽象方法,让子类自行实现
nextScope:
如果下一节点不为空,则执行下一个节点的doScope方法;
如果下一节点为空但上一节点不为空,则执行上一节点的doHandle(这里就引导回顶端链节点了);
如果上一节点和下一节点均为空,则执行doHandle方法(这种情况就只有一个节点,只需执行自己的doHandle即可)
nextHandle:如果下一节点不为空,同时当前节点是当前处理器,则执行下一个节点的doHandle方法;否则如果当前执行器不为空,则执行父类HandlerWrapper的处理方法。这里第一个分支_nextScope != null && _nextScope != _handler的场景是,假设A、B、C均为ScopedHandler,但X不是ScopedHandler,形成了一个A、B、X、C的链,由于前期我们的A、B、C会形成以下关系:
A._outScope = null
A._nextScope = B
B._outScope = A
B._nextScope = C
X由于不是ScopedHandler,因此不会存在_outScope和_nextScope属性
C._outScope = A
C._nextScope = null
当A.handle时,会触发A.doScope,同时A的nextScope不是null,因此继续B.doScope,B的nextScope也不是null,因此执行C.doScope;此时C.nextScope是null,但outScope不是null,因此执行A.doHandle,由于A.nextScope不是null并且A.handler = B,所以执行B.doHandle;由于B.nextScope不是null,但B.nextScope是C,并不是之前设置的X,因此会直接执行HandlerWrapper的handle方法,这时会执行X.handle,X执行完成后,会执行自己的handler,也就是C,于是C.handle,由于C是ScopedHandler,同时C的outScope不是null,因此直接进入doHandle,C完成执行后发现nextScope为null,因此停止执行,这样其实就构建了ScopedHandler的注释里面提到的A、B、X、C链的处理逻辑,
即:A.handle -> A.doScope -> B.doScope -> C.doScope -> A.doHandle -> B.doHandle -> X.handle -> C.handle -> C.doHandle
五、总结
ScopedHandler作为Jetty生态中Handler组件的关键成员,用一个handle方法完成整条链的节点的顺序前置准备(doScope)和各节点的顺序执行(doHandle),也算是Jetty特色下的设计思路。通常一个应用会使用WebAppContext(实际就是ServletContextHandler的扩展)作为主Handler,该Handler会同时开启SessionHandler、SecurityHandler、ServletHandler,并会构建一个执行链,通过这个链会最终调用到我们的业务Servlet(在Spring MVC里面其实就是调用到了我们的DispatcherServlet)。后续会重点讲解SessionHandler、ServletHandler,我会带领大家探索Session管理以及Filter链及Servlet的执行
博客已迁移至 http://www.ph0ly.com 欢迎大家访问新的网站,文章更加齐全~