在 tomcat的原理-组件connector中,我们说CoyteAdapter类的sevice方法中 将request 和response 交给容器处理,那tomcat的container结构又是怎样的呢?
总述
先来张container的架构图:
1.container是一个接口, 它用于接收request 并返回response对象的一种容器
2.engine 是servlet引擎 ,是tomcat 容器中的顶级容器 ,管理多host。
- context 是servlet 的上下文,代表独立的应用程序。一个context可以有多个servlet。
- wrapper 是代表单独的servlet。
- tomcat中采用父子关系设计这些容器。为什么要这样设计?
a.正好符合一个engine多host , 多 context ,多servlet 这种结构.
b.分层架构,各司其职。
c.组合模式 ,你中有我,我中有你。 - 最后我们用server.xml 来理解下tomcat 中的container关系:
分析
回顾上篇我们参数一个web请求访问tomcat会经过如下组件:
- accptor: 接收网络请求,发送事件给poller组件。
- poller: 接收I/O事件, 丢给业务线程池。
- processor: 处理socket。
- adapter :适配 httpServletReqeust 和 httpServletResponse
- 交给container 执行pipline。
来说说tomcat 的pipeline机制。
1.pipeline的结构?
本质就是个链表
初始状态: pipeline中用 两个属性 Valve first , Valve basic , first == null
初次添加 valve(假设叫a) : 给first 赋值 a 并将basic 连接到first后面
再次添加 valve(假设叫b): basic 连到b之后, b连接到basic 原来的前节点。
代码如下 : 类StandardPipeline 的addValve()
2.pipeline如何初始化的?
在container 实例化的时候,各个容器的pipeline完成初始化, 并设置基础的basic valve。
ContainerBase属性中:
StandardEngine类构造器中:
3.何时调用pipline?
在CoyoteAdapter类中的service方法中调用tomcat的pipline机制.
a. 由container的父子图我们知道,connector.getService().getContainer() 我们可以获取到 standardEngine对象
b. getFirst().invoke() 如果first == null , 直接调用basic valve 的invoke() ;否则 我们会在pipeline 的valve链上一直调用他的next valve 的invoke方法,直到最后一个basic valve 。
c. standardEngine 对象的pipleline属性的basic 为StandardEngineValve对象 , 会调用host 容器 的pipeline中的各个valve 直到 basicValve 。 过程和Engine容器的 pipeline一致。
d. 我们直接看StandardHostValve 的invoke() 方法 。
e.在看 StandardContextValve 的 invoke() 方法, 同 StandardHostValve 的invoke() 方法 ,同理调用Context容器中的每个valve。
在StandardContextValve 类中可以直接从request对象中获取到wrapper,我们思考一个问题 request.getWrapper()是如何将 wapper 封装到request对象中的??
结论: 在container的生命周期中,会使用发布订阅模式,发送事件,Mapper组件通过监听的方式去注册容器 , reqeust 对象在封装的时候从mapper 中匹配所需的容器对象装载到MappingData中,代码如下。
1.service中初始化mapper 和 mapperListner。
-
mapperListner 实现了 ContainerListener, LifecycleListener接口
- 在容器的生命周期方法中或添加子容器方法中会触发mapperListener 的containerEvent()或lifecycleEvent() ,会将容器注册到Mapper中
-
当tomcat 内部的request 在经过CoyteAdapter的service方法的时, 会调用Mapper的map方法将容器设置到request的MappingData中:
f.context容器是如何添加子容器wrapper的?
我们知道wrapper 对应与我们web.xml 的servlet ,对于一个应用来说 我们可以配置很多servlet ,如果使用degister【后面分析tomcat lifecycle 再谈】 配置放入到context 中是不切实际的,
tomcat 通过发布订阅模式 监听器的方式来给context 配置子容器,上代码:
1.在StandardContext 的startInternal() 方法中, 会发送Lifecycle.CONFIGURE_START_EVENT 的事件
-
ContextConfig 实现了LifecycleListener 接口,意味着当StandardContext 发送CONFIGURE_START_EVENT 事件后 ,ContextConfig会处理事件。
-
configureStart() 方法中调用了webconfig() 方法 , 在webConfig()方法中, 会扫描web.xml 文件 , WebXmlParser 解析文件, 加载ServletContainerInitializers , 配置context 的 过滤器FilterDef 等信息, 创建Wrapper 容器并添加到子容器中等操作
ContextConfig 的webconfig()方法:
这样context 就可以在start()生命周期方法中 , 去调用子容器wrapper 的start()方法了
总结:context 使用事件发送给contextConfig 配置context 属性/ 子容器等信息
g. 最后我们看下StandardWapperValve 的 invoke() 方法。
我们看下wrapper 是如何创建servlet的?
-
加载servlet
实例工厂创建servlet 对象 , 其中servletClass 是contextConfig 解析web.xml 的时候set 到wrapper 中的。
- servlet 初始化 loadServlet方法中 调用了 initServlet(servlet)
为什么使用 门面模式 StandardWrapperFacade?
个人理解 :StandardWrapper 实现了servletConfig接口 ,StandardWrapperFacade 也实现了 servletConfig 接口,
但是StandardWrapper 中的很多wrapper 的细节 撸码者并不想暴露给servlet 所以使用 facade模式包装 StandardWrapper ,及只暴露servlet 所关心的。
到这里我们的servlet 已经创建好了, 在很久以前我开发中经常遇到乱码的问题 , 当时我记得写filter 来解决tomcat的乱码问题,在tomcat 中在调用我们的servlet 之前有一系列的filter , 下面我们看看tomcat 实现的Filter机制。
1.使用工厂模式创建拦截器链filterChain , 将web.xml中的配置的filter加入到filterChain的 filters 数组中 并 将n(filter数)自增1 , pos 用来记录当前执行的连接器下标。
filterChain的doFilter()方法中: 当 n< pos 的时候 pos ++ , 并找到下一个filter【filter1】 继续执行 filter1 的dofilter()方法 , 再找 下下个filter【filter2】 继续执行 filter2 的dofilter()方法 ,依次类推 直到 n == pos
当 n== pos 的时候调用servlet的service方法处理业务
来回顾下责任链模式, 写个demo:
-
定义fiter 接口
-
定义拦截链
- 创建拦截器A B C 都实现了Filter 接口
-
main 方法测试 :
-
输出
6.结论
总结
- tomcat 通过Adapter 的service 方法将 request 和response 交给 Engine Host Context wrapper 容器
- 每个容器中包含一个pipleLine ,而pipleLine 由多个valve 和一个标准BasicValue(StandardHostValve ...)
- Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter() 方法,最终会调到 Servlet的 service方法。
来张图吧:
后面我们记录下tomcat 的生命周期和打破双亲委派机制 , 有错误的地方,请大佬指正。