加载Servlet的类不等于创建Servlet实例,Tomcat先加载Servlet的类,然后还得在Java堆创建Servlet实例。
一个Web应用里往往有多个Servlet,而在Tomcat中一个Web应用对应一个Context容器,即一个Context容器需管理多个Servlet实例。
但Context容器并不直接持有Servlet实例,而是通过子容器Wrapper管理Servlet,可以把Wrapper容器看作Servlet的包装。
为何需要Wrapper?Context容器直接维护一个Servlet数组还不满足?
Servlet不仅是个类实例,还有相关配置信息,比如URL映射、初始化参数,因此设计出了一个包装器,把Servlet本身和它相关的数据包起来。
管理好Servlet就够了吗?
Listener和Filter也是Servlet规范,Tomcat也要创建它们的实例,在合适时机调用它们的方法。
Servlet管理
Tomcat用Wrapper容器管理Servlet
protected volatile Servlet instance = null;
它拥有一个Servlet实例,Wrapper#loadServlet实例化Servlet:
public synchronized Servlet loadServlet() throws ServletException { Servlet servlet; // 1. 创建Servlet实例 servlet = (Servlet) instanceManager.newInstance(servletClass); // 2.调用了Servlet#init【Servlet规范要求】 initServlet(servlet); return servlet; }
为加快系统启动速度,一般采取资源延迟加载,所以Tomcat默认情况下Tomcat在启动时不会加载你的Servlet,除非把Servlet loadOnStartup参数置true。
虽然Tomcat在启动时不会创建Servlet实例,但会创建Wrapper容器。当有请求访问某Servlet,才会创建该Servlet实例。
Servlet是被谁调用呢?
Wrapper作为一个容器组件,有自己的Pipeline和BasicValve,其BasicValve为StandardWrapperValve。
当请求到来,Context容器的BasicValve会调用Wrapper容器中Pipeline中的第一个Valve,然后调用到StandardWrapperValve:
public final void invoke(Request request, Response response) { // 1.实例化Servlet servlet = wrapper.allocate(); // 2.给当前请求创建一个Filter链 ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); // 3. 调用这个Filter链,Filter链中的最后一个Filter会调用Servlet filterChain.doFilter(request.getRequest(), response.getResponse()); }
StandardWrapperValve的invoke就三步:
- 创建Servlet实例
- 给当前请求创建一个Filter链
- 调用Filter链
为何要给每个请求创建Filter链
每个请求的请求路径不同,而Filter都有相应路径映射,因此不是所有Filter都需要处理当前请求,要根据请求路径选择特定的一些Filter。
为何没调用Servlet#service
Filter链的最后一个Filter会负责调用Servlet。
Filter管理
跟Servlet一样,Filter也可在web.xml
配置。
但Filter的作用域是整个Web应用,因此Filter的实例维护在Context容器:Map里存的是filterDef(filter定义),而非filter类实例
Filter链存活期很短,它跟每个请求对应。一个新请求来了,就动态创建一个Filter链,请求处理完,Filter链就被回收。
public final class ApplicationFilterChain implements FilterChain { // Filter链的Filter数组 private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // Filter链的当前的调用位置 private int pos = 0; // Filter总数 private int n = 0; // 每个Filter链最终要调用的Servlet private Servlet servlet = null; public void doFilter(ServletRequest req, ServletResponse res) { internalDoFilter(request,response); } private void internalDoFilter(ServletRequest req, ServletResponse res){ if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this); return; } servlet.service(request, response); }