Servlet工作原理解析2(以Tomcat为例)

      Servlet如何工作

      Servlet是如何被加载的、如何被初始化的,以及Servlet的体系结构都已经清楚了,现在就看看它是如何被调用的。

      用户从浏览器向服务器发起的一个请求通常会包含如下信息:http://hostname: port/contextpath/serletpath,hostname和port用来与服务器建立TCP连接,后面的URL才用来选择在服务器中那个子容器服务用户的请求。服务器是如何根据这个URL来到达正确的Servlet容器中的呢?在Tomcat7中这件事很容易解决,因为这种映射工作由专门一个类来完成,这个类就是org.apache.tomcat.util.http.mapper,这个类保存了Tomcat的Container容器中的所有子容器的信息,org.apache.catalina.connector.Request类在进入Container容器前,Mapper将会根据这次请求的hostname和contextpath将host和context容器设置到Request的mappingData属性中,如下图所示。所有当Request进入Container容器前,要访问哪个子容器就已经确定了。

 

      但是在Mapper中怎么会有容器的完整关系呢?这要看MapperListener类的初始化过程了,如下代码:

      public void init(){

           findDefaultHost();

           Engine  engine  =  (Engine)connector.getService().getContainer();

           engine.addContainerListener(this);

           Container[]  conHosts  =  engine.findChildren();

           for(Container  conHost  :  conHosts){

                 Host  host  =  (Host)conHost;

                 if(!LifecycleState.NEW.equals(host.getState())){

                      host.addLifecycleListener(this);

                      registerHost(host);

                 }

           }          

      }

      这段代码是将MapperListener类作为一个监听者加到整个容器的每个子容器中,这样只要任何一个容器发生变化,MapperListener都将会被通知到,相应的保存容器关系的MapperListener的mapper属性也会被修改。在for循环中就是将host及下面的子容器注册到mapper中。

      下图是Request在容器中的路由图:

 

      请求是如上图到达正确的Wrapper容器的,但是在请求到达最终的Servlet前还要完成一些步骤,必须要执行Filter链,以及通知你在web.xml中定义的listener。接下来要执行Servlet的service方法了。通常我们自己定义的servlet并不直接去实现javax.servlet.servlet接口,而是去继承更简单的HttpServlet类或者GenericServlet类,这样可以有选择地覆盖相应的方法去实现要完成的工作。

      Servlet的确已经能够帮我们完成所有的工作了,但是现在的Web应用很少直接将交互的全部页面用Servlet来实现,而是采用更加高效的MVC框架来实现。它们的基本原理是将所有的请求都映射到一个Servlet,然后去实现service方法,这个方法也就是MVC框架的入口。

      当Servlet从Servlet容器中移除时,也就表明该Servlet的生命周期结束了,这时Servlet的destroy方法将被调用,做一些扫尾工作。

 

 

      Servlet中的Listener

      在整个Tomcat服务器中,Listener使用得非常广泛,它是基于观察者模式设计的,能够从另一个纵向维度控制程序和数据,是一种快捷的手段。

      如下图,Servlet中的Listener:

 

      实际上这6个Listener都继承了EventListener接口,每个Listener各自定义了需要实现的接口。这些个接口基本上涵盖了整个Servlet生命周期的各种事件。这些Listener的实现类可以配置在web.xml的 <listener>标签中,也可以在应用程序中动态添加Listener,需要注意的是ServletContextListener在容器启动后就不能再添加新的,因为它监听的事件不会再出现了。掌握这些Listener的使用方法,能够让我们的程序设计的更加灵活。如spring的org.springframework.web.context.ContextLoaderListener就实现了一个ServletContextListener,当容器加载时启动spring容器。ContextLoaderListener在contextInitialized方法中初始化spring容器,有几种办法可以加载spring容器,通过在web.xml的<context-param>标签中配置spring的applicationContext.xml路径,文件名可以任意取,如果没有配置,将在/WEB/INF/路径下查找默认的applicationContext.xml文件。ContextLoaderListener的contextInitialized方法代码如下:

      public  void  contextInitialized(ServletContextEvent  event){

           this.contextLoader  =  createContextLoader();

           if(this.contextLoader  ==  null){

                  this.contextLoader  =  this;

           }

           this.contextLoader.initWebApplicationContext(event.getServlet-Context());

      }

 

 

 

      Filter如何工作

      Filter也是在web.xml中另外一个常用的配置项,可以通过<filter>和<filter-mapping>组合来使用它。实际上Filter可以完成与Servlet同样的工作,甚至比Servlet使用起来更加灵活,因为它除了提供了request和response外,还提供了一个FilterChain对象,这个对象让我们更加灵活地控制请求的流转。

      在Tomcat中,FilterConfig和FilterChain的实现类分别是ApplicationFilterConfig和ApplicationFilterChain,而Filter的实现类有用户自定义,只要实现Filter接口中定义的三个接口就行,这三个接口在与Servlet中的类似。只不过还有一个ApplicationFilterChain类,它可以将多个Filter串联起来,组成一个链,这个链与Jetty中的Handler链有异曲同工之妙。下面是Filter类中的三个接口方法:

      1.init(FilterConfig):初始化接口,在用户自定义的Filter初始化时被调用,它与Servlet的init方法的作用是一样的,FilterConfig与ServletConfig也类似,除了都能渠道容器的环境类ServletContext对象外,还能获取在<filter>下配置的<init-param>参数值。

      2.doFilter(ServletRequest, ServletResponse, FilterChain):在每个用户请求进来时这个方法都会被调用,并在Servlet的service方法前被调用。而FilterChain就代表当前的整个请求链,所以可以通过FilterChain.doFilter()将请求继续传递下去。如果想拦截这个请求,就不调用FilterChain.doFilter(),那么这个请求就不返回了。所以Filter是一种责任链模式。

      3.destroy:当Filter对象被销毁时,这个方法被调用。注意,当Web容器调用这个方法之后,容器会再调用一次doFilter方法。

 

      Filter类的核心还是传递的FilterChain对象,这个对象保存了到最终Servlet对象的所有Filter对象,这些对象都保存在ApplicationFilterChain对象的filters数组中。在FilterChain链上每执行一个Filter对象,数组的当前计数都会加1,知道计数等于数组的长度,当FilterChain上所有的Filter对象执行完成后,就会执行最终的Servlet。所以在ApplicationFilterChain对象中会持有Servlet对象的引用。下图是Filter对象的执行时序图:

      Servlet中url-pattern

      在web.xml中<servlet-mapping>和<filter-mapping>都有<url-pattern>配置项,它们的作用都是匹配一次请求是否会执行这个Servlet或者Filter,那么这个URL是怎么匹配的呢?又是何时匹配的呢?

      一个请求最终被分配到一个servlet中是通过org.apache.tomcat.util.http.Mapper类完成的,这个类会 根据请求的URL来匹配在每个Servlet中配置的<url-pattern>,所以它在一个请求被创建时就已经匹配了。Filter的url-pattern匹配是在创建ApplicationFilterChain对象时进行的,它会把所有定义的Filter的url-pattern与当前的url匹配如果匹配成功就将这个Filter保存到ApplicationFilterChain的filter数组中,然后在FilterChain中依次调用。

      在web.xml加载时,首先会检查<url-pattern>配置是否符合规则,这个检查是在StandardContext的validateURLPattern方法中进行的,如果检查不成功,Context容器启动会失败,并且会报java.lang.IllegalArgumentException:Invalid<url-pattern>/a/*.htm in Servlet mapping错误。

      <url-pattern>的解析规则,对Servlet和Filter是一样的,匹配规则有如下三种:

      1.精确匹配:如/foo.htm只会匹配foo.htm这个URL

      2.路径匹配:如/foo/*会匹配以foo为前缀的URL

      3.后缀匹配:如*.htm会匹配所有以.htm为后缀的URL

      Servlet的匹配规则在org.apache.tomcat.util.http.mapper.Mapper.internalMapWrapper中定义,对Servlet的匹配来说如果同时定义了多个<url-pattern>,那么到底匹配哪个Servlet呢?这个匹配顺序是:首先精确匹配,然后是路径匹配,最后是后缀匹配。

      Filter的匹配规则在ApplicationFilterFactory.matchFiltersURL方法中定义。Filter的匹配原则和Servlet有些不同,只有匹配成功,这些Filter都会在请求链上被调用。

      

 

posted @ 2016-09-23 22:19  西风.烈马  阅读(2169)  评论(0编辑  收藏  举报