Tomcat与SpringMVC结合分析(一)
关键字:
Bootsrap,Catalina,Server,Service,Engine,Host,Context,Wrapper,Valve,Pipeline,ContextConfig,ServletConfig,ContextLoaderListener,DispatcherServlet.
一:环境配置
我用的分析的工具是eclipse,源码用maven的自动下载源码的功能.然后在maven中添加相应的dependence.这样但我们ctrl+leftmouse的时候就会自动帮我们下载源代码.
SpringMVC版本为3.2.4,虽然现在基本用的是Spring4.x或是SpringBoot,但基本的原理没有变动太大所以有典型性.
Tomcat的版本为8.0.47.
servlet-api的版本为3.1.0.
二:Tomcat的基本原理

1.调用在Bootstrap中的主函数main()程序入口调用自身的start()方法,在start()方法中实例化org.apache.catalina.startup.Catalina类调用其start()方法,在Catalina的start()中调用load()载入tomcat文件夹目录下conf文件夹下的server.xml并创建Server类,然后调用Server的start()方法,到这里我们的tomcat就运行起来了.
2.Server类代表整个Tomcat服务器,这里有必要介绍一下为什么Tomcat叫Servlet容器,在Tomcat中包含四个容器,分别为Engine,Host,Context,Wrapper.Engine是Host的父容器,依次类推.四者都继承自Container接口.一般在Tomcat中一个Engine实例中包含一个Host实例,一个Host实例中包含一个Context实例,一个Context代表一个WEB程序并包含多个Wrapper实例,一个Wrapper代表一个Servlet类.
3.一次请求的大题流程是这样的,首先由Connector获取到Http请求,封装ServletRequest和ServletResponse对象并派发给Context管道中的所有Valve,执行完所有Valve后执行基础Valve根据请求的url映射到对应的Wrapper,然后调用个Wrapper管道中的所有Valve,最后调用基础Valve在如这个Wrapper类所封装的Servlet,实例化后调用器Service方法对用户的请求进行处理,最后将结果负载在response中返回给用户.
三:从源码开始分析Tomcat运行原理
1.Bootsrap作为整个Tomcat的入口中的main()方法,doemon为Bootstrap类型.


在start()中载入并实例化Catalina并调用了start()方法.

在start()中调用load()方法载入了Server类,然后调用了其start()方法启动Tomcat.

2.下面让我们仔细分析Tomcat是怎么以优雅的方式配置Server类的.

在createStartDigester内部

这里先解释一下Digester是一个用于解析xml的小框架,内部通过封装sax解析来实现的.介绍一下Digester的功能:
A.在遇到patter对应的标签时创建对应的对象,将对象压栈,然后标签结束后再将这个对象出栈.

通过上面的代码配置在遇到<Server>标签时创建StandardServer对象,并将该对象压进Digester内部的stack中,遇到</Server>的时候将StandardServer对象出栈.
B.开启对应标签的attribute属性填充到实例化的类中的功能.

C.这是最关键的地方,在遇到对应的匹配规则后在end()的方法调用栈顶下面紧挨着的对象的指定的方法并将栈顶的对象作为参数穿进去.

刚开始的时候我很迷惑,因为在整个程序中都没有看到Catalina什么时候显式的设置了Server类型的属性,后来我才发现这个秘密:还是在Catalina的load()方法中.
在真正开始Digester的parse()方法之前先将自身压进了自身的栈中,然后我们再看下面

我们结合Tomcat中conf目录下的server.xml文件,<Server>就是根元素.所以解析最后出栈的肯定是<Server>标签对应的StandardServer对象.我接着看这几行代码.
因为最开始将Catalina类自身以this传了进来所以栈底是Catalina对象,<server>标签结束的时候会调用Ctalina的setServer()方法将StandardServer对象的实例作为参数传进来.到这里我们成功设置了server属性,调用StandardServer的start()方法.
3.StandardServer中的start()方法继承自LifecycleBase然后又调用自己的startInternal().

在startInternal()中启动了由Digester从server.xml中解析出来的默认的Service组件
再看一下Catalina中的代码

遇到<Service>后会实例化StandardService实例然后在解析</Service>结束后会将StandardService的实例添加到StandardServer实例中.
4.StandardService中一次调用了StandardEngine和StandardHost的start()方法,但是我们始终找不到StandardContext是什么时候被初始化的让我们看看程序是怎么创建StandardContext实例的.

还是回到Catalina初始化Digester实例时的代码,我们看对于<Host>标签加的一些规则

这里的比较核心的就是HostConfig,它继承自LifecycleListenerRule,当StandardHost启动的时候会调用监听器的启动事件的处理逻辑.
我们认真对待这块,当调用StandardHost的start()方法实际上调用的是父类LifecycleBase的start()方法.

这里调用子类的startInternal()方法,StandardHost和ContainerBase都实现了这个方法,根据多态规则首先会调用最底层子类的方法

然后在这里又会调用父类ContainerBase类的startInternal()方法,让我们看这个方法.

这个方法会出发生命周期start的状态.

在setStateInternal()的方法中获取上面的枚举类型的LifecycleState中String类型的成员变量,STARTING类型对应Lifecycle.START_EVENT类型的事件,然后看fireLifecycleEvent()方法.

这里将实践类型传给了LifecycleSupport中维护的LifecycleListener类型的集合,并调用其中核心方法lifecycleEvent().
所以回到上面创建Digester的时候我们定义了将匹配规则,在解析到<Host>的时候会开始实例化HostConfig类型的LifecycleListener并将其添加到StandardHost当中.然后在start()方法调用后会触发HostConfig中的lifecycleEvent()方法.

调用start()方法.
在start()中调用了deployApps()方法.

让我们直接看Tomcat我们部署的项目的载入方式,项目的文件夹为webapps.

向Digester解析生成的线程池中提交一个DeployDirectory类的实例.
在里面又调用了HostConfig类的deployDirectory()方法.

在deployDirectory()方法中前面对路径进行了一系列的判断

让我们看看这个路径.

有时候我们的的项目的相关目录下并没有这个文件.

所以直接进入了else块直接生成了StandardContext实例初始化参数后直接添加到了父容器StandardHost中.
5.StandardContext类的初始化
让我们先看看在HostConfig中是怎么配置StandardContext的.

首先先给他添加了一个生命周期事件监听器.

在StandardContext中我们找到了这个监听器,ContextConfig.想必到这里大家如果看懂前面HostConfig的执行原理,这个也不难理解,在这里我就不仔细的介绍了,当Context启动的时候肯定会出发start类型的时间,然后调用ContextConfig的lifecycleEvent()方法.让我们看看在这个ContextConfig中都做了什么.


调用configureStart()方法中调用webConfig()载入我们WEB-INF文件夹下面的web.xml.

这里也是用Digester进行解析的,添加的匹配规则无非是对Servlet,Filter等我们在开发中经常用到的配置标签的解析并将生成的实体类填充在WebXml类型的实例中.还有一点值得提示的是在第一次获取ServletContext的时候会对其进行初始化为ApplicationContext.

最后在configureContext方法中将填充完毕的WebXml实例中的相关数据填充到Context中.

下面的代码接着上面的.

这里看Servlet填充的过程,用Wrapper包装Servlet再将这些Wrapper填充到Context中.
让我们接着看在StandardContext的startInternal()我们比较感兴趣的地方.

这里会将以字符串数组保存的listener数组进行初始化.

对实例化的listener进行分类

对初始化的监听器分类我们这里关心ServletContextListener类型的监听器,判断如果符合该类型的监听器后便会加入方法中临时创建的名为lifecycleListeners的ArrayList中.

设置刚生成的lifecycleListenders.
生成事件,调用ServletContextListener类型实例的contextInitialized()方法,这就是Tomcat和SpringMVC勾搭开始的地方.具体的内容我们在下一篇讲,睡了.
Tomcat与SpringMVC结合分析(一)的更多相关文章
- SpringBoot中Tomcat和SpringMVC整合源码分析
概述 SpringBoot中集成官方的第三方组件是通过在POM文件中添加组件的starter的Maven依赖来完成的.添加相关的Maven依赖之后,会引入具体的jar包,在SpringBoot启动 ...
- Servlet和Tomcat底层源码分析
Servlet 源码分析 Servlet 结构图 Servlet 和 ServletConfig 都是顶层接口,而 GenericServlet 实现了这两个顶层接口,然后HttpServlet ...
- Tomcat 8.5 架构分析
官方文档:Apache Tomcat 8 Architecture 以下分析的是 Version 8.5. Tomcat 组件关系图 根据 Architecture Overview 绘制: Serv ...
- spring boot 加载web容器tomcat流程源码分析
spring boot 加载web容器tomcat流程源码分析 我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>o ...
- tomcat与springmvc 结合 之---第17篇 StandContext容器和SpringMVC的WebApplicationContext的联系
writedby 张艳涛, 上一篇分析了,dispatcherservlet通过getServletConfig 方法获取了web.xml定义的<param-init>属性的过程 那么在如 ...
- 三思考,实现自己定义404页:Tomcat、SpringMVC精确匹配、重写DispatchServlet
第1种方式:Tomcat直接处理 web.xml <error-page> <error-code>404</error-code> <location> ...
- springmvc(一) springmvc框架原理分析和简单入门程序
springmvc这个框架真的非常简单,感觉比struts2还更简单,好好沉淀下来学习~ --WH 一.什么是springmvc? 我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻的理解 ...
- IDEA+Tomcat+Maven+SpringMVC基于Java注解配置web工程
1.在IDEA中新建Maven工程,使用archetype. 2.添加Maven依赖 <dependencies> <dependency> <groupId>ju ...
- SpringMVC学习(一)———— springmvc框架原理分析和简单入门程序
一.什么是springmvc? 我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻的理解这个思想,struts2在web层,spring在中间控制,hibernate在dao层与数据库打交 ...
随机推荐
- netty使用从0到1
本周强总在组内做了netty分享,内容相当不错,趁着这次分享记录的,以及以前研究,进行一下记录. java io形式存在三种,一种是BIO传统IO是阻塞IO,面向字符.字节服务都属于这一种.NIO官方 ...
- JSON数据解析及gson.jar包
从服务器端接收数据的时候,那些数据必须以浏览器能够理解的格式来发送. 服务器端的编程语言只能以如下 3 种格式返回数据: HTML XML JSON JSON一种简单的数据格式,比xml更轻巧. JS ...
- 在websphere上部署集群应用程序-工作记录
1) 创建web集群.client集群,添加集群托管节点,根据需求来,我的需求是两个应用部署到4个服务器上,属于1主3备模式 2) 创建webspere变量:选择你需要的集群作用域,新建资源 (作 ...
- 用R画有图例的中国地图
近期在网上找了几种画中国地图方法,终于认为这种方法还是最适用的 1.用googlevis包.因为中国国情如今已经不能訪问google地图了.所以大多中国用户来说仅仅能望洋兴叹了. 2.用ggplot包 ...
- [Shell]crontab 运行任务调用shell脚本,相对路径无法找到
问题出现的场景大概就是 1 cron调用一个python脚本 2 python脚本中调用一个shell脚本(对日志分析)获取shell输出然后发送邮件 类似一个监控任务. 直接运行python脚本 ...
- 自学Zabbix3.10.1.3-事件通知Notifications upon events-媒介类型Jabber
自学Zabbix3.10.1.3-事件通知Notifications upon events-媒介类型Jabber Jabber有第三方插件,能让Jabber用户和MSN.YahooMessager. ...
- AJAX扩展-POST传递参数并跳转页面
拓展的代码: 这段代码的原理是创建一个表单,所有args都创建一个隐藏的input,用post方法把这些参数传递过去 注意form表单一定要加载到页面中,即下面代码中标红的部分,不然参数是无法被传递的 ...
- 「mysql优化专题」90%程序员没听过的存储过程和存储函数教学(7)
一.MYSQL储存过程简介(技术文): 储存过程是一个可编程的函数,它在数据库中创建并保存.它可以有SQL语句和一些特殊的控制结构组成.当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时 ...
- intellij 打开node项目 一直停留在scanning files to index....,或跳出内存不够的提示框
说明: 在npm install 后,会出现Scanning files to index ...... 出现这个是正常的,但是一直不消失就不正常了.原因是npm install 后 node_mod ...
- Error updating database. Cause: java.sql.BatchUpdateException: Field 'id' doesn't have a default value
异常信息 ### Error updating database. Cause: java.sql.BatchUpdateException: Field 'id' doesn't have a de ...