Servlet工作原理解析

Tomcat中真正管理Servlet的是Context容器,一个Context对应一个Web工程,在Tomcat的配置文件中可以很容易发现:

<Context path-"" docBase="" reloadable="true"/>

Tomcat7开始支持嵌入式方式,通过添加一个启动类org.apache.catalina.startup.Tomcat,创建一个这个类的实例对象并调用start方法就可以很容易启动Tomcat(SpringBoot的内嵌Tomcat容器启动大概就基于此),还可以通过这个对象来添加和修改Tomcat的配置参数,如动态添加Context,Servlet等

Servlet容器启动过程

添加一个Web应用时将会创建一个StandardContext对象,这个对象就是Tomcat中管理Servlet的Context具体对象了。并且会给这个容器对象设置必要的参数,如url和path分别对应这个应用在Tomcat中的访问路径和这个应用实际的物理路径

Tomcat的启用逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle(生命周期)接口,它管理着容器的整个生命周期,所有容器的修改和状态的改变都会由它去通知已经注册的观察者(Listener)

Web应用的初始化工作

Web应用的初始化工作是在ContextConfigconfigureStart方法中实现的,引用用的初始化主要是解析web.xml文件,这个文件描述了一个Web应用的关键信息,也是一个Web应用的入口

web.xml文件中的各个配置项会被解析成相应的属性保存在WebXml对象中,如果当前的应用支持Servlet3.0还会完成一些额外的操作,这些操作主要是Servlet3.0新特性的解析以及对annotations注解的支持

接下来将WebXml对象中的属性设置到Context容器中,当然这里包括创建Servlet对象,Filter,Listener等

在Tomcat中Servlet会被包装成StandardWrapper,这是因为StandardWrapper是Tomcat容器的一部分,具有容器的特性,而Servlet作为独立的Web开发标准,不应该强耦合在Tomcat里,这里主要是为了解耦

除了将Servlet包装成StandardWrapper并作为子容器添加到Context外,其他所有的web.xml属性都被解析到Context中,所以说Context才是真正运行Servlet的地方

一个Web应用对应一个Context容器,容器的配置属性由应用的web.xml指定

创建Servlet实例

创建Servlet对象

如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化

在Tomcat的conf下的web.xml文件(默认配置文件)定义了两个Servlet,分别是org.apache.catalina.servlets.DefaultServletorg.apache.jasper.servlet.JspServlet。它们的load-on-startup分别是1和3,当Tomcat启动的时候这两个Servlet就会启动

创建Servlet实例的方法是从Wrapper.loadServlet开始的。oadServlet方法要完成的就是获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象。如果这个Servle配置了jsp-file,那么这个ServletClass就是JspServlet了(web.xml中配置Servlet不仅可以是一个Servlet类,也可以是一个JSP页面,JSP毕竟也是Servlet)

初始化Servlet

初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单,就是调用Servlet的init()方法,同时把包装了的StandardWrapperFacade作为ServletConfig传入Servlet,这里先注意下这个StandardWrapperFacade

如果该Servlet关联的是一个JSP文件,那么初始化就是创建Servlet时说的JspServlet,接下来回模拟一次简单请求,请求调用这个JSP文件,以便编译JSP文件为类,并初始化这个类

Servlet体系结构

Java Web应用是基于Servlet规范运转的,Servlet规范主要关联的有三个类,分别是ServletConfig,ServletRequest和ServletResponse。这三个类是通过容器传递给Servlet的,其中ServletConfig 在Servlet初始化时就传给Servlet了,而后面两个是在请求达到时调用Servlet传递过来

ServletConfig

ServletConfig接口中声明的方法会发现,这些方法都是为了获取Servlet的一些配置属性,而这些配置可能在Servlet运行时用到,ServletConfig是在Servlet init时由容器传过来的。在Tomcat中StandardWrapperStandardWrapperFacade都实现了ServletConfig接口,而StandardWrapperFacadeStandardWrapper门面类。所以传给Servlet是实际上是StandardWrapperFacade对象,这个类能够保证从StandardWrapper中拿到ServletConfig所规定的数据,而又不把ServletConfig不关心的数据暴露给Servlet

通过Servlet可以拿到Context容器中的一些必要信息,如应用的工作路径,容器支持的Servlet最小版本等

ServletRequest和ServletResponse

在创建自己的Servlet类时通常使用的都是HttpServletRequest和HttpServletResponse接口,它们继承了ServletRequest和ServletResponse。而实际在Tomcat中,接收到请求将首先会创建org.apache.coyote.Requestorg.apache.coyote.Response,它们是一个轻量级的类,作用就是在服务器接受到请求后,经过简单解析将这个请求快速分配给后续线程去处理,所以它们的对象很小,很容易被JVM回收。接下来当交给一个用户线程去处理这个请求时又创建org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response对象,这两个对象一直贯穿整个Servlet容器直到要传给Servlet,传给Servlet的时Request和Response的门面类RequestFacadeResponseFacade,这里用的也是门面模式

Servlet如何工作

一个请求如下格式:http://hostname:port/contextpath/servletpath,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属性中,所以当Requst进入Container容器之前,对于它需要访问访问哪个容器就已经确定了

在请求达到最终的Servlet前还需要完成一些步骤,必须要执行的Filter链,以及通知在web.xml中定义的Listener

接下来要执行Servlet的service方法,通常情况下,我们自己定义的servlet并不直接去javax.servlet.servlet接口,而是去继承更简单的HttpServlet类或者GenericServlet类,我们可以有选择地覆盖相应的方法去实现要完成的工作

现在的Web应用很少直接将交互直接用Servlet来实现,而是采用更加高效的MVC框架来实现。这个MVC框架的基本原理是将所有的请求都映射到一个Servlet上,然后去实现service方法,这个方法也就是MVC框架的入口(如SpringMVC的DispatcherServlet)。当然也有用Filter作为框架的入口Struct2,不过struct2框架基本上没人使用了

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

Listener

Listener是基于观察者模式设计的,目前Listener提供了两类6种观察者接口,分别是:EventListeners(事件)类型的ServletContextAttributeListener,ServletRequestAttributeListener,ServletRequestListener,HttpSessionAttributeListener;和LifecycleListeners(生命周期)类型的ServletContextListener,HttpSessionListener

Listener的实现类可以配置在web.xml的<listener>标签中,当然也可以在应用程序中动态添加Listener,需要注意的是ServletContextListener在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现了

如Spring的org.springframework.web.context.ContextLoaderListener就实现了一个ServletContextListener,当容器加载时启动Spring容器。ContextLoaderListener在contextInitialized方法中初始化Spring容器。通过在web.xml的<context-param>标签中配置Spring的applicationContext.xml路径,文件名可以任意取,如果没有配置,将在/WEB-INF/路径下查找默认的applicationContext.xml文件

Filter

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

在Tomcat容器中,FilterConfig和FilterChain的实现类分别的实现类分别是ApplicationFilterConfigApplicationFilterChain,而Filter的实现类由用户自定义,只要实现Filter接口中定义的三个接口就行。ApplicationFilaterChain类将多个Filter串联起来组成一个链

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

Servlet中的url-pattern

在Web.xml中<servlet-mapping><filter-mapping>都有<url-pattern>配置项,它们的作用都是匹配一次请求是否会执行这个Servlet或Filter

之前说一个请求最终被分配到一个Servlet中是通过org.apache.tomcat.util.http.Mapper类完成的,这个类会根据请求的URL来匹配在每个Servlet中配置的<url-pattern>,所以它在一个请求被创建出来时就已经匹配了

Filter的url-pattern匹配是在创建ApplicationFilterChain对象时进行的,它会把所有定义的Filter的url-pattern与当前的URL匹配,如果匹配成功就将这个Filter保存在ApplicationFilterChain的filters数组中,然后在FilterChain中依次调用

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

  • 精准匹配:如/abc.html只会匹配abc.html这个URL
  • 路径匹配:如/abc.*会匹配所有以abc作为前缀的URL
  • 后缀匹配:如*.html会匹配所有以.html为后缀的URL

Servlet的匹配规则在org.apache.tomcat.util.http.mapper.Mapper类的internalMapWrapper方法定义的,对Servlet的匹配来说如果同时定义了多个<url-pattern>,那么到底匹配哪个Servlet呢?这个匹配规则顺序是:首先精准匹配,如果精准匹配不成功就会使用第二原则"最长路径匹配",最后根据后缀进行匹配。但是一个请求只会成功匹配到一个Servlet

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

posted @ 2021-01-30 14:53  OverZeal  阅读(682)  评论(0编辑  收藏  举报