立体的萌 | 作者
Java技术迷 | 出品
java Web经过多年发展已经从成为了互联网应用技术的“网红”,而Servlet作为java Web技术的扛把子,我们不能不了解,正是因为有了Servlet,才造就了Tomcat的发达,进而造就了互联网世界的繁荣
Servlet与Servlet容器的关系就好比枪和子弹的关系,是必须配套使用的,那如何在一起使用呢?官方的解释是:通过接口的方式
我当初学习Servlet的时候是和Tomcat差不多一起学的,因为Tomcat就是个Servlet容器嘛,所以我以Tomcat为例
Tomcat容器分为4个等级,真正管理Servlet的容器是Context容器,一个Context容器(也就是Servlet运行时的Servlet容器)就对应着一个Web工程,我当初刚开始学java的时候,我的老师就教给我们如何手动的配置,在Tomcat的配置文件里写下面这一行代码:
经典的Tomcat7增加了支持嵌入式的功能,具体来说就是增加了一个启动类:org.apache.catalina.startup.Tomcat。创建出一个实例对象之后再调用start()函数就可以启动了Tomcat了
下面就创建一个Tomcat实例再添加一个Web应用:
当Context容器初始化状态设置为init,添加到Context容器Listener将会被调用,ContextConfig类会负责整个Web应用的配置文件的解析工作,ContextConfig类继承了Lifecyclelistener接口(他是在调用Tomcat,addWebapp时被加入到StandardContext容器中的)
ContextConfig的init函数无非就这么几个功能:
ContextConfig的init函数的工作做完之后,Context容器就会执行startInternal函数,这个函数的工作相对复杂一些:
第五步:修改启动状态,通知感兴趣的观察者(Web应用的配置);
第七步:获取ServletContext并设置必要的参数;
第八步:初始化”load on startup”的Servlet;
Web应用的初始化由ContextConfig的configureStart函数来实现,其实初始化没什么复杂的,就是解析个web.XML文件,web.xml文件之所以很重要是因为描述了一个web应用的关键信息,同事还是一个web应用的人口
一般来说,Tomcat会先找globalWebXml,再找hostWebXml,最后寻找应用的配置文件,该找的人都找全之后,把webxml对象中的属性设置到Context容器中
必须要把Servlet包装成Context容器中的StandardWrapper并作为子容器添加到Context中,,这里有一个问题就是为什么不包装成Servlet对象呢?因为StandardWrapper是Tomcat容器中的一部分,也就是说StandardWrapper也是具有容器的特征的,Servlet不一样,他可是一个独立的Web开发标准,不应该强耦合到Tomcat之中
然后再把其他所有的web.xml属性都被解析到Context中,真正运行着Servlet的Servlet容器是Context
没有完成实例化那也只是万里长征第一步而已,实例化一共分为两步:先创建Servlet对象再进行初始化
在解析配置文件时会读取默认的globalWebXml,在Conf下的web.xml文件中定义了一些默认的配置项,这里面定义了两个Servlet:org.apache.catalina.servlets.DefaultServelt、org.apache.jasper.servelt.JspServelt,前者的load-on-startup是1,后者的load-on startup是3,如果Servelt的load-on-startup的配置项大于0的话,那么在Context容器启动的时候就会被实例化
初始化的工作是通过StardardWrapper的initServelt函数来完成的,这个函数了里面调用了Servelt的init函数,还把包装了StandardWrapper对象的StandardWrapperFacade作为config传给Servelt
与Servelt关联的就是这三个类:ServletConfig、ServeltRequest和ServeltResponse,他们都是通过容器先后传递给Servelt的,ServeltConfig早在Servelt初始化的时候就传给Servelt,后两个则是在请求到达时调用Servelt传递过来的
Servelt的运行模式是个典型的”握手型交互式”的运行模式,什么意思呢?就是两个模块为了交换数据通常会准备一个交易场景,这个场景会一直跟随这个交易过程,直到交易完成,交易场景的初始化是根据本次的交易对象的参数来定制的,其实说是参数,其实就是个配置类
所以,交易场景都是由ServeltContext来描述,而参数集合则是由ServletConfig来描述,ServeltRequest和ServeltResponse就是要交互的具体对象,可以比喻成传输数据的大货车
这个问题也可以换成Servelt是如何被调用的,我们都是客户端要想请求服务端的服务就必须在浏览器里面输入URL,那么,服务器是如何根据客户端给定的URL来到达正确的Servelt容器的呢?
Tomcat7.0中有一个专门的类来完成映射工作:org.apache.tomcat.util.http.mapper,之所以用这个类来完成是因为这个类包含了Cntainer容器中的所有子容器的信息
这是把MapperListener类作为一个监听者加到一整个Container容器的每个子容器中
在org.apache.catalina.connector.Request类进入Container容器之前,Mapper会事先根据这次请求的hostnane和contextpath把host和context容器设置到Request的mappingData属性中,所以,在Request进入Container容器之前就已经确定好要访问的子容器了
基于观察者模式创建的Listener应用非常广泛,话不多说直接上图:
这些listener的实现类配置在web.xml<Listener>标签中,也可以在应用程序中动态的添加,这是ContextLoaderListener的contextInitialize()函数:
不过一定要注意,ServeltContextListener在容器启动之后就不能再添加新的,原因很简单,他所监听的事件不会再出现了
Filter就好比我们进入商场或者图书馆时候的存包的动作,它都是通过<filter>和<filter-mapping>结合起来使用的,Servelt能够完成的工作,Filter也一样能完成,而且还比前者更加的灵活,因为他还有一个FilterChain对象,这个对象可以更加灵活的控制请求的流转
FilterChain对象保留了到最终Servelt对象的所有的Filter对象,这些对象全部保存在了ApplicationfilterChain对象的filter数组中,在FilterChain每执行一个Filter对象,数组的当计数术就会加1,直到计数等于数组的长度,所有的对象执行完毕后,就会执行最终的Servelt
Filter的url匹配在创建ApplicatonFilterChain对象时进行的,如果匹配成功了就会把Filter保存在ApplicationFilterChain的filter数组中,然后在filterchain中依次调用
在<web.xml>加载时StandardContext的validateURLPattern函数进行检查<url-pattern>是否符合规则,如果不符合的话,Context容器启动就会失败
这就是大名鼎鼎的Servelt的工作原理,明白了他可以说就明白了一半Tomcat
,今日互联网的世界的繁荣离不开像Servelt这样的技术的发展,我简直不敢想象没有互联网的世界,在今天互联网就像水、空气一样成为人们生活必不可少的一部分了。
以上为个人经验,希望能给大家一个参考,如有错误或未考虑完全的地方,望不吝赐教。
回复关键字【电子书】领取资料
程序媛社区
整理一些有关程序员干货,辅助广大爱学习人士,网罗一些资料,并将这些资料分享给更多有需要的人。
3篇原创内容
Official Account