tomcat想必大家都非常的了解,在这里就不必介绍,在现在应用的springboot框架,tomcat就被内嵌其中。tomcat是怎么运行起来的?你知道怎么拓展tomcat吗?
tomcat启动流程
springboot加载流程
首先我们看一下springboot中tomcat的启动流程
1.找到AbstractApplicationContext方法中refresh方法,这个也是spring的启动流程,在refresh方法中,有一个onRefresh()方法,这个方法在spring中是一个空方法,在springboot对这个方法进行了拓展。
1 | public void refresh() throws BeansException, IllegalStateException { |
springboot拓展的onRefresh()方法如下
1 | @Override |
很简单,看方法命名就知道这个创建了一个web容器,默认也就是tomcat容器。
在creatWebServer()方法中
1 | private void createWebServer() { |
this.webServer = factory.getWebServer(getSelfInitializer());
在这里进行了详细的创建过程,打开这个方法的具体实现,如下就是具体的tomcat的实现
1 | @Override |
先看一下tomcat的整体架构
在tomcat中,最外层的就是tomcat,tomcat中有一个server。server中有多个service,
每个service可以单独对外服务。
service中有connector和container两个组件,关系如下
connector又分为endpoint和processor
endpoint处理传输层协议,演示版本为nio实现
processor处理应用层协议,演示版本为http1.1实现
中间通过适配器模式把request转换成servletRequest
全tomcat围绕着lifecycle接口进行组件生命周期的维护
在lifecycle接口中又有init,start等方法,tomcat初始化和启动就会围绕着这些方法进行实现
tomcat流程图就以深入理解java web技术内幕中servlet启动为例
(ps:需要注意的是网上有很多图是错误的,错误图中标明host和context的init方法在engine init之后启动)
下面我们详细的了解下tomcat中源码执行的过程
TomcatWebServer.initialize()方法中
1 | this.tomcat.start(); |
正式开启tomcat的启动流程
tomcat启动流程
init过程
每一个容器都有自己的标准实现
在tomcat中start方法开启server.start();
server.start()方法实现为LifecycleBase.start()方法
调用init方法
init方法调用了initInternal()方法
1 | @Override |
initInternal方法为抽象方法,实现为子类的实现,谁调用谁实现,刚才为
StandardServer调用的,所有也由它实现
1 | protected abstract void initInternal() throws LifecycleException; |
在这里又用到了一个设计模式
模版方法模式
把通用的抽取到父类,子类只关注自己不同的实现即可
在后面的容器中全部运用了这种模版方法模式
包括init start stop方法等,后面就不在详细描述
在server标准实现中,StandardServer的initInternal具体实现
1 | { |
在service初始化的过程中,主要有三步
- engine的初始化
1 | engine.init(); |
- executor的初始化
1 | executor.init(); |
- connector的初始化
1 | for (Connector connector : connectors) { |
engine的初始化
在engine初始化中,没有做什么特别的事,最主要的是并没有对后面host的初始化,对网上engine会对host进行初始化的纠正
executor的初始化也只是把自己这个组件加载进监控中,本身并没有做任何任何重要的事
重点看connector的初始化过程
在connect初始化中,有一个组件叫做protocolHandler:protocolHandler是Coyote协议的接口,通过endpoint和processor实现了对具体协议的处理能力。
在第二框中加载了具体的CoyoteAdapter实例
1 | super.initInternal(); |
然后进行protocolHandler的初始化
1 | protocolHandler.init(); |
protocolHandler继续对endpoint进行初始化
1 | endpoint.init(); |
前面我们说到endpoint是对传输层协议的实现,进去看是否有这样的实现
bindWithCleanup();方法,里面是bind()方法,具体的实现需要由子类实现,在这里是nio实现
bind里面也就是对socket的初始化和打开,具体可自己观看相应实现
start过程
在上面介绍了init的过程,下面接着介绍start过程
start一样,也是通过模版方法模式,主要看每个组件的startInternal方法实现
1 | StandardServer.start() |
循环调用services[i].start()方法
service标准实现StandardService
也是和init方法一样,对engine、exector、connector的start方法进行调用
在engine start方法中,还对host、context、wapper组件进行了初始化
exector start方法中对线程池进行了初步的设置,创建了自己的线程工厂,在后续任务中使用
1 | @Override |
Connector.start()调用流程如下
protocolHandler.start()->endpoint.start()->startInternal()交给具体的实现,这里还是nioEndpoint
我们进NioEndpoint start看一下关键点实现
NioEndpoint start方法中,对执行的线程池进行了创建
1 | if (getExecutor() == null) { |
1 | public void createExecutor() { |
我们看一下里面的具体参数,这也是tomcat对业务线程的默认参数
1 | getMinSpareThreads() |
里面的实现就是取最大线程和最小空闲线程的最小值,最大线程默认200,最小空闲线程默认10,取得值为10,所以tomcat核心线程默认为10
1 | getMaxThreads() |
最大线程默认为200
1 | initializeConnectionLatch(); |
设置tomcat最大链接10000
1 | startAcceptorThread(); |
监听socket套接字,由于nio的多路复用机制,所以可以用极少的线程监听大量的socket
1 | serverSock.accept(); |
至此start核心流程基本结束
servlet运行流程
在socket监听到客户端信息之后,交由线程池处理
执行doRun方法
再到processor获取Coyote执行service方法
在coyote中转换request和response参数为servlet的ServletRequest和ServletResponse
1 | postParseSuccess = postParseRequest(req, request, res, response); |
在postParseRequest方法中对路径进行了查找,找到对应的servlet,里是通过mapper组件完成的
1 | connector.getService().getMapper().map(serverName, decodedURI, |
把转换成功之后的request和response设置到管道中进行执行
1 | connector.getService().getContainer().getPipeline().getFirst().invoke( |
在管道中也运用了责任链模式,这和filter中是一样的,具体的大家可以单独研究这一块源码
每一个pipeline中都有一个大量的Valve,但是只有一个基础Valve
基础的Valve一般以容器+StandardValve命名,比如StandardengineValve
基础Valve调用下一层的容器阀。如上图所示
在CoyoteAdapter中开始调用enginePipeline开始
在engine中又开始吊桶host的第一个Valve
1 | host.getPipeline().getFirst().invoke(request, response); |
接下来的都是如此
filter和servlet执行流程
在StandardWrapperValve中开始构建filter链和servlet
首先在StandardWrapperValve invoke方法中
1 | servlet = wrapper.allocate(); |
通过allocate反射获取servlet
再到
1 | ApplicationFilterChain filterChain = |
通过工厂获取filter链
把servlet设置进filterchain
在filterchain设置filterConfig
1 | filterChain.addFilter(filterConfig); |
循环添加filter
1 | void addFilter(ApplicationFilterConfig filterConfig) { |
然后执行internalDoFilter方法
1 | internalDoFilter(request,response); |
这个时候就真正的开始执行filter和servlet了
执行filter和servlet的时候,会循环执行filter,直到最后执行servlet
执行流程大体如此,每次执行filter pos次数+1,执行全部执行,然后开始执行servlet
整个tomcat流程执行完毕
tomcat涉及到的设计模式
当然tomcat细节远远不止如此,详细讲解可以出一本书,有兴趣的可以去了解下,tomcat中包含了特别多的设计模式
比如
- 观察者模式,tomcat中对组件的监听都是基于观察者模式进行的
- 模版方法模式:基本上全篇都是模版方法模式,在业务上我们也是常用这个模式
- 适配器模式:在request转换到servletRequest的过程中,就使用了CoyoteAdapter进行了适配
- 责任链模式:在tomcat中,pipeline和Valve组合,filter都是责任链模式的具体实现